diff --git a/documentation/pom.xml b/documentation/pom.xml index cd9cbe97a07..bb6d9fed308 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -268,6 +268,29 @@ + + org.codehaus.mojo + exec-maven-plugin + + + pagefind-index + + exec + + process-resources + + npx + + -y + pagefind + --site + ${project.build.outputDirectory} + + + + + + maven-assembly-plugin @@ -499,6 +522,22 @@ none + + pagefind-index + + exec + + process-resources + + npx + + -y + pagefind + --site + ${project.build.outputDirectory} + + + diff --git a/documentation/src/main/resources/_data/sidebars/ditto_sidebar.yml b/documentation/src/main/resources/_data/sidebars/ditto_sidebar.yml index bc058224ac1..7f4e252caa7 100644 --- a/documentation/src/main/resources/_data/sidebars/ditto_sidebar.yml +++ b/documentation/src/main/resources/_data/sidebars/ditto_sidebar.yml @@ -5,712 +5,416 @@ entries: product: Eclipse Ditto™ folders: - - title: Introduction + - title: Getting Started output: web - folderitems: - title: Overview url: /intro-overview.html output: web type: homepage - - title: Digital twins + - title: Digital Twins Explained url: /intro-digitaltwins.html output: web - - title: Hello world + - title: Hello World Tutorial url: /intro-hello-world.html output: web + - title: Building Ditto + url: /installation-building.html + output: web + - title: Running Ditto + url: /installation-running.html + output: web - - title: Release Notes + - title: Core Concepts output: web folderitems: - - title: 3.8.12 - url: /release_notes_3812.html - output: web - - title: 3.8.11 - url: /release_notes_3811.html - output: web - - title: 3.8.10 - url: /release_notes_3810.html - output: web - - title: 3.8.9 - url: /release_notes_389.html - output: web - - title: 3.8.8 - url: /release_notes_388.html - output: web - - title: 3.8.7 - url: /release_notes_387.html - output: web - - title: 3.8.6 - url: /release_notes_386.html - output: web - - title: 3.8.5 - url: /release_notes_385.html - output: web - - title: 3.8.4 - url: /release_notes_384.html - output: web - - title: 3.8.3 - url: /release_notes_383.html - output: web - - title: 3.8.2 - url: /release_notes_382.html - output: web - - title: 3.8.1 - url: /release_notes_381.html - output: web - - title: 3.8.0 - url: /release_notes_380.html - output: web - - title: 3.7.6 - url: /release_notes_376.html - output: web - - title: 3.7.5 - url: /release_notes_375.html - output: web - - title: 3.7.4 - url: /release_notes_374.html - output: web - - title: 3.7.3 - url: /release_notes_373.html - output: web - - title: 3.7.2 - url: /release_notes_372.html - output: web - - title: 3.7.1 - url: /release_notes_371.html - output: web - - title: 3.7.0 - url: /release_notes_370.html + - title: Overview + url: /basic-overview.html output: web subfolders: - - title: Archive + - title: Model Entities output: web subfolderitems: - - title: 3.6.11 - url: /release_notes_3611.html - output: web - - title: 3.6.10 - url: /release_notes_3610.html - output: web - - title: 3.6.9 - url: /release_notes_369.html - output: web - - title: 3.6.8 - url: /release_notes_368.html - output: web - - title: 3.6.7 - url: /release_notes_367.html + - title: Thing + url: /basic-thing.html output: web - - title: 3.6.5 - url: /release_notes_365.html + - title: Feature + url: /basic-feature.html output: web - - title: 3.6.4 - url: /release_notes_364.html + - title: Policy + url: /basic-policy.html output: web - - title: 3.6.3 - url: /release_notes_363.html + - title: Namespaces and Names + url: /basic-namespaces-and-names.html output: web - - title: 3.6.2 - url: /release_notes_362.html + - title: Metadata + url: /basic-metadata.html output: web - - title: 3.6.1 - url: /release_notes_361.html + + - title: Communication + output: web + subfolderitems: + - title: Signals + url: /basic-signals.html output: web - - title: 3.6.0 - url: /release_notes_360.html + - title: Command + url: /basic-signals-command.html output: web - - title: 3.5.12 - url: /release_notes_3512.html + - title: Command Response + url: /basic-signals-commandresponse.html output: web - - title: 3.5.11 - url: /release_notes_3511.html + - title: Error Response + url: /basic-signals-errorresponse.html output: web - - title: 3.5.10 - url: /release_notes_3510.html + - title: Event + url: /basic-signals-event.html output: web - - title: 3.5.9 - url: /release_notes_359.html + - title: Announcement + url: /basic-signals-announcement.html output: web - - title: 3.5.8 - url: /release_notes_358.html + - title: Messages + url: /basic-messages.html output: web - - title: 3.5.7 - url: /release_notes_357.html + - title: Change Notifications + url: /basic-changenotifications.html output: web - - title: 3.5.6 - url: /release_notes_356.html + - title: Signal Enrichment + url: /basic-enrichment.html output: web - - title: 3.5.5 - url: /release_notes_355.html + + - title: Access Control + output: web + subfolderitems: + - title: Authentication & Authorization + url: /basic-auth.html output: web - - title: 3.5.4 - url: /release_notes_354.html + - title: Checking Permissions + url: /basic-auth-checkpermissions.html output: web - - title: 3.5.3 - url: /release_notes_353.html + + - title: Querying + output: web + subfolderitems: + - title: Search + url: /basic-search.html output: web - - title: 3.5.2 - url: /release_notes_352.html + - title: RQL Expressions + url: /basic-rql.html output: web - - title: 3.5.1 - url: /release_notes_351.html + - title: Conditional Requests + url: /basic-conditional-requests.html output: web - - title: 3.5.0 - url: /release_notes_350.html + + - title: Connections + url: /basic-connections.html + output: web + + subfolders: + - title: Advanced Capabilities + output: web + subfolderitems: + - title: Acknowledgements / QoS + url: /basic-acknowledgements.html output: web - - title: 3.4.5 - url: /release_notes_345.html + - title: History + url: /basic-history.html output: web - - title: 3.4.4 - url: /release_notes_344.html + - title: Placeholders + url: /basic-placeholders.html output: web - - title: 3.4.3 - url: /release_notes_343.html + - title: Errors + url: /basic-errors.html output: web - - title: 3.4.2 - url: /release_notes_342.html + - title: Data By-Pass + url: /advanced-data-by-pass.html output: web - - title: 3.4.1 - url: /release_notes_341.html + - title: APIs + url: /basic-apis.html output: web - - title: 3.4.0 - url: /release_notes_340.html + + - title: Architecture + output: web + folderitems: + - title: Overview + url: /architecture-overview.html + output: web + + subfolders: + - title: Services + output: web + subfolderitems: + - title: Policies + url: /architecture-services-policies.html output: web - - title: 3.3.7 - url: /release_notes_337.html + - title: Things + url: /architecture-services-things.html output: web - - title: 3.3.6 - url: /release_notes_336.html + - title: Things-Search + url: /architecture-services-things-search.html output: web - - title: 3.3.5 - url: /release_notes_335.html + - title: Connectivity + url: /architecture-services-connectivity.html output: web - - title: 3.3.4 - url: /release_notes_334.html + - title: Gateway + url: /architecture-services-gateway.html output: web - - title: 3.3.3 - url: /release_notes_333.html + + - title: Connectivity + output: web + folderitems: + - title: Overview + url: /connectivity-overview.html + output: web + + subfolders: + - title: Manage Connections + output: web + subfolderitems: + - title: via HTTP API + url: /connectivity-manage-connections.html output: web - - title: 3.3.2 - url: /release_notes_332.html + - title: via Piggyback Commands + url: /connectivity-manage-connections-piggyback.html output: web - - title: 3.3.0 - url: /release_notes_330.html + + - title: Protocol Bindings + output: web + subfolderitems: + - title: AMQP 0.9.1 + url: /connectivity-protocol-bindings-amqp091.html output: web - - title: 3.2.1 - url: /release_notes_321.html + - title: AMQP 1.0 + url: /connectivity-protocol-bindings-amqp10.html output: web - - title: 3.2.0 - url: /release_notes_320.html + - title: MQTT 3.1.1 + url: /connectivity-protocol-bindings-mqtt.html output: web - - title: 3.1.2 - url: /release_notes_312.html + - title: MQTT 5 + url: /connectivity-protocol-bindings-mqtt5.html output: web - - title: 3.1.1 - url: /release_notes_311.html + - title: HTTP 1.1 + url: /connectivity-protocol-bindings-http.html output: web - - title: 3.1.0 - url: /release_notes_310.html + - title: Kafka 2.x + url: /connectivity-protocol-bindings-kafka2.html output: web - - title: 3.0.0 - url: /release_notes_300.html + - title: Eclipse Hono + url: /connectivity-protocol-bindings-hono.html output: web - - title: 2.4.2 - url: /release_notes_242.html + + - title: Data Transformation + output: web + subfolderitems: + - title: Payload Mapping + url: /connectivity-mapping.html output: web - - title: 2.4.1 - url: /release_notes_241.html + - title: Header Mapping + url: /connectivity-header-mapping.html output: web - - title: 2.4.0 - url: /release_notes_240.html + + - title: Security + output: web + subfolderitems: + - title: TLS Certificates + url: /connectivity-tls-certificates.html output: web - - title: 2.3.2 - url: /release_notes_232.html + - title: SSH Tunneling + url: /connectivity-ssh-tunneling.html output: web - - title: 2.3.1 - url: /release_notes_231.html + - title: HMAC Signing + url: /connectivity-hmac-signing.html output: web - - title: 2.3.0 - url: /release_notes_230.html - output: web - - title: 2.2.2 - url: /release_notes_222.html - output: web - - title: 2.2.1 - url: /release_notes_221.html - output: web - - title: 2.2.0 - url: /release_notes_220.html - output: web - - title: 2.1.3 - url: /release_notes_213.html - output: web - - title: 2.1.2 - url: /release_notes_212.html - output: web - - title: 2.1.1 - url: /release_notes_211.html - output: web - - title: 2.1.0 - url: /release_notes_210.html - output: web - - title: 2.0.1 - url: /release_notes_201.html - output: web - - title: 2.0.0 - url: /release_notes_200.html - output: web - - title: 1.5.1 - url: /release_notes_151.html - output: web - - title: 1.5.0 - url: /release_notes_150.html - output: web - - title: 1.4.0 - url: /release_notes_140.html - output: web - - title: 1.3.0 - url: /release_notes_130.html - output: web - - title: 1.2.1 - url: /release_notes_121.html - output: web - - title: 1.2.0 - url: /release_notes_120.html - output: web - - title: 1.1.5 - url: /release_notes_115.html - output: web - - title: 1.1.3 - url: /release_notes_113.html - output: web - - title: 1.1.2 - url: /release_notes_112.html - output: web - - title: 1.1.1 - url: /release_notes_111.html - output: web - - title: 1.1.0 - url: /release_notes_110.html - output: web - - title: 1.0.0 - url: /release_notes_100.html - output: web - - title: 0.9.0 - url: /release_notes_090.html - output: web - - title: 0.8.0 - url: /release_notes_080.html - output: web - - title: 1.0.0-M2 - url: /release_notes_100-M2.html - output: web - - title: 1.0.0-M1a - url: /release_notes_100-M1a.html - output: web - - title: 0.9.0-M2 - url: /release_notes_090-M2.html - output: web - - title: 0.9.0-M1 - url: /release_notes_090-M1.html - output: web - - title: 0.8.0-M3 - url: /release_notes_080-M3.html - output: web - - title: 0.8.0-M2 - url: /release_notes_080-M2.html - output: web - - title: 0.8.0-M1 - url: /release_notes_080-M1.html - output: web - - title: 0.3.0-M2 - url: /release_notes_030-M2.html - output: web - - title: 0.3.0-M1 - url: /release_notes_030-M1.html - output: web - - title: 0.2.0-M1 - url: /release_notes_020-M1.html + + - title: APIs & Interfaces + output: web + folderitems: + - title: Overview + url: /httpapi-overview.html + output: web + - title: HTTP API Concepts + url: /httpapi-concepts.html + output: web + - title: HTTP API Search + url: /httpapi-search.html + output: web + - title: HTTP API Messages + url: /httpapi-messages.html + output: web + - title: WebSocket Binding + url: /httpapi-protocol-bindings-websocket.html + output: web + - title: Server-Sent Events + url: /httpapi-sse.html + output: web + - title: Cloud Events Binding + url: /httpapi-protocol-bindings-cloudevents.html + output: web + - title: Explorer UI + url: /user-interface.html + output: web + + subfolders: + - title: Client SDKs + output: web + subfolderitems: + - title: SDK Overview + url: /client-sdk-overview.html output: web - - title: 0.1.0-M3 - url: /release_notes_010-M3.html + - title: Java SDK + url: /client-sdk-java.html output: web - - title: 0.1.0-M1 - url: /release_notes_010-M1.html + - title: JavaScript SDK + url: /client-sdk-javascript.html output: web - - title: Installation + - title: WoT Integration output: web folderitems: - - - title: Building Ditto - url: /installation-building.html - output: web - - title: Running Ditto - url: /installation-running.html + - title: WoT Overview + url: /basic-wot-integration.html output: web - - title: Operating Ditto - url: /installation-operating.html + - title: WoT Validation Config + url: /basic-wot-validation-config.html output: web - - title: Extending Ditto - url: /installation-extending.html + - title: WoT Integration Example + url: /basic-wot-integration-example.html output: web - - title: Basic concepts + - title: Operating & Extending output: web - folderitems: - - title: Overview - url: /basic-overview.html + - title: Operating Ditto + url: /installation-operating.html output: web subfolders: - - title: Model entities + - title: Operating Details output: web subfolderitems: - - title: Thing - url: /basic-thing.html - output: web - - title: Feature - url: /basic-feature.html + - title: Configuration + url: /operating-configuration.html output: web - - title: Policy - url: /basic-policy.html + - title: Authentication + url: /operating-authentication.html output: web - - title: Namespaces and Names - url: /basic-namespaces-and-names.html + - title: MongoDB + url: /operating-mongodb.html output: web - - title: Thing Metadata - url: /basic-metadata.html + - title: Monitoring & Tracing + url: /operating-monitoring.html output: web - - title: Errors - url: /basic-errors.html + - title: DevOps Commands + url: /operating-devops.html output: web - - title: Authentication and Authorization - url: /basic-auth.html + - title: Extending Ditto + url: /installation-extending.html output: web - subfolders: - - title: Auth patterns - output: web - subfolderitems: - - title: Checking Permissions for Resources - url: /basic-auth-checkpermissions.html - output: web - - - title: Messages - url: /basic-messages.html + - title: Ditto Protocol + output: web + folderitems: + - title: Overview + url: /protocol-overview.html + output: web + - title: Twin / Live Channel + url: /protocol-twinlive.html + output: web + - title: Specification + url: /protocol-specification.html + output: web + - title: Topic Structure + url: /protocol-specification-topic.html + output: web + - title: Errors + url: /protocol-specification-errors.html output: web - - title: Signals - url: /basic-signals.html + - title: Bindings + url: /protocol-bindings.html + output: web + - title: Things Group + url: /protocol-specification-things.html output: web subfolders: - - title: Signal types + - title: Commands & Events output: web subfolderitems: - - title: Command - url: /basic-signals-command.html + - title: Create / Modify + url: /protocol-specification-things-create-or-modify.html output: web - - title: Command response - url: /basic-signals-commandresponse.html + - title: Merge + url: /protocol-specification-things-merge.html output: web - - title: Error response - url: /basic-signals-errorresponse.html + - title: Retrieve + url: /protocol-specification-things-retrieve.html output: web - - title: Event - url: /basic-signals-event.html + - title: Delete + url: /protocol-specification-things-delete.html output: web - - title: Announcement - url: /basic-signals-announcement.html + - title: Acknowledgements + url: /protocol-specification-acks.html output: web - - title: Signal enrichment - url: /basic-enrichment.html + - title: Search + url: /protocol-specification-things-search.html + output: web + - title: Messages + url: /protocol-specification-things-messages.html + output: web + - title: Streaming Subscriptions + url: /protocol-specification-streaming-subscription.html output: web - - title: APIs - url: /basic-apis.html - output: web - - title: Connections - url: /basic-connections.html - output: web - - title: Placeholders - url: /basic-placeholders.html - output: web - - title: Change notifications - url: /basic-changenotifications.html - output: web - - title: RQL expressions - url: /basic-rql.html - output: web - - title: Conditional requests - url: /basic-conditional-requests.html - output: web - - title: Search - url: /basic-search.html - output: web - - title: History capabilities - url: /basic-history.html - output: web - - title: Acknowledgements / QoS - url: /basic-acknowledgements.html + - title: Policies Group + url: /protocol-specification-policies.html output: web subfolders: - - title: WoT (Web of Things) + - title: Commands & Announcements output: web subfolderitems: - - title: WoT integration - url: /basic-wot-integration.html + - title: Create / Modify + url: /protocol-specification-policies-create-or-modify.html output: web - - title: WoT validation config API - url: /basic-wot-validation-config.html + - title: Retrieve + url: /protocol-specification-policies-retrieve.html output: web - - title: WoT integration example - url: /basic-wot-integration-example.html + - title: Delete + url: /protocol-specification-policies-delete.html + output: web + - title: Announcement + url: /protocol-specification-policies-announcement.html output: web - - title: Advanced concepts - output: web - - folderitems: - - title: Data By-Pass - url: /advanced-data-by-pass.html + - title: Connections Group + url: /protocol-specification-connections.html output: web - - title: Architecture - output: web + subfolders: + - title: Announcements + output: web + subfolderitems: + - title: Announcement + url: /protocol-specification-connections-announcement.html + output: web - folderitems: - - title: Overview - url: /architecture-overview.html + - title: Examples + url: /protocol-examples.html output: web subfolders: - - title: Services + - title: Things Examples output: web subfolderitems: - - title: Policies - url: /architecture-services-policies.html - output: web - - title: Things - url: /architecture-services-things.html + - title: Create a Thing + url: /protocol-examples-creatething.html output: web - - title: Things-Search - url: /architecture-services-things-search.html + - title: Delete a Thing + url: /protocol-examples-deletething.html output: web - - title: Connectivity - url: /architecture-services-connectivity.html - output: web - - title: Gateway - url: /architecture-services-gateway.html - output: web - - - title: HTTP API - output: web - - folderitems: - - title: Overview - url: /httpapi-overview.html - output: web - - title: Concepts - url: /httpapi-concepts.html - output: web - - title: Search - url: /httpapi-search.html - output: web - - title: Messages - url: /httpapi-messages.html - output: web - - title: WebSocket protocol binding - url: /httpapi-protocol-bindings-websocket.html - output: web - - title: Cloud Events HTTP protocol binding - url: /httpapi-protocol-bindings-cloudevents.html - output: web - - title: Server sent events - url: /httpapi-sse.html - output: web - - - title: Connectivity API - output: web - folderitems: - - title: Overview - url: /connectivity-overview.html - output: web - subfolders: - - title: Manage connections - output: web - subfolderitems: - - title: via HTTP API - url: /connectivity-manage-connections.html - output: web - - title: via Piggyback commands - url: /connectivity-manage-connections-piggyback.html - output: web - - title: AMQP 0.9.1 protocol binding - url: /connectivity-protocol-bindings-amqp091.html - output: web - - title: AMQP 1.0 protocol binding - url: /connectivity-protocol-bindings-amqp10.html - output: web - - title: MQTT 3.1.1 protocol binding - url: /connectivity-protocol-bindings-mqtt.html - output: web - - title: MQTT 5 protocol binding - url: /connectivity-protocol-bindings-mqtt5.html - output: web - - title: HTTP 1.1 protocol binding - url: /connectivity-protocol-bindings-http.html - output: web - - title: Kafka 2.x protocol binding - url: /connectivity-protocol-bindings-kafka2.html - output: web - - title: Hono connection binding - url: /connectivity-protocol-bindings-hono.html - output: web - - title: Payload mapping - url: /connectivity-mapping.html - output: web - - title: Header mapping - url: /connectivity-header-mapping.html - output: web - - title: TLS certificates - url: /connectivity-tls-certificates.html - output: web - - title: SSH tunneling - url: /connectivity-ssh-tunneling.html - output: web - - title: HMAC signing - url: /connectivity-hmac-signing.html - output: web - - - title: Client SDK - output: web - folderitems: - - title: Overview - url: /client-sdk-overview.html - output: web - - title: Java - url: /client-sdk-java.html - output: web - - title: JavaScript - url: /client-sdk-javascript.html - output: web - - - title: Ditto Protocol - output: web - - folderitems: - - title: Overview - url: /protocol-overview.html - output: web - - title: Twin/live channel - url: /protocol-twinlive.html - output: web - - title: Specification - url: /protocol-specification.html - output: web - - title: Protocol topic - url: /protocol-specification-topic.html - output: web - - title: Errors - url: /protocol-specification-errors.html - output: web - - title: Things group - url: /protocol-specification-things.html - output: web - - subfolders: - - title: → commands/events - output: web - subfolderitems: - - title: Create/Modify - url: /protocol-specification-things-create-or-modify.html - output: web - - title: Merge - url: /protocol-specification-things-merge.html - output: web - - title: Retrieve - url: /protocol-specification-things-retrieve.html - output: web - - title: Delete - url: /protocol-specification-things-delete.html - output: web - - title: Acknowledgements - url: /protocol-specification-acks.html - output: web - - - title: → search/messages - output: web - subfolderitems: - - title: Search - url: /protocol-specification-things-search.html - output: web - - title: Messages - url: /protocol-specification-things-messages.html - output: web - - - title: Policies group - url: /protocol-specification-policies.html - output: web - - subfolders: - - title: → commands/announcements - output: web - subfolderitems: - - title: Create/Modify - url: /protocol-specification-policies-create-or-modify.html - output: web - - title: Retrieve - url: /protocol-specification-policies-retrieve.html - output: web - - title: Delete - url: /protocol-specification-policies-delete.html - output: web - - title: Announcement - url: /protocol-specification-policies-announcement.html - output: web - - - title: Connections group - url: /protocol-specification-connections.html - output: web - - subfolders: - - title: → announcements - output: web - subfolderitems: - - title: Announcement - url: /protocol-specification-connections-announcement.html - output: web - - - title: Streaming subscriptions (history) - url: /protocol-specification-streaming-subscription.html - output: web - - - title: Bindings - url: /protocol-bindings.html - output: web - - - title: Examples - url: /protocol-examples.html - output: web - - subfolders: - - title: → Things examples - output: web - subfolderitems: - - title: Create a Thing - url: /protocol-examples-creatething.html - output: web - - title: Delete a Thing - url: /protocol-examples-deletething.html - output: web - - title: Modify a Thing - url: /protocol-examples-modifything.html + - title: Modify a Thing + url: /protocol-examples-modifything.html output: web - title: Retrieve a Thing url: /protocol-examples-retrievething.html @@ -718,7 +422,7 @@ entries: - title: Retrieve multiple Things url: /protocol-examples-retrievethings.html output: web - - title: Modify the Policy ID of a Thing + - title: Modify Policy ID url: /protocol-examples-modifypolicyid.html output: web - title: Create Attributes @@ -796,211 +500,532 @@ entries: - title: Create Feature Properties url: /protocol-examples-createproperties.html output: web - - title: Delete Feature Properties - url: /protocol-examples-deleteproperties.html + - title: Delete Feature Properties + url: /protocol-examples-deleteproperties.html + output: web + - title: Modify Feature Properties + url: /protocol-examples-modifyproperties.html + output: web + - title: Retrieve Feature Properties + url: /protocol-examples-retrieveproperties.html + output: web + - title: Create a single Property + url: /protocol-examples-createproperty.html + output: web + - title: Delete a single Property + url: /protocol-examples-deleteproperty.html + output: web + - title: Modify a single Property + url: /protocol-examples-modifyproperty.html + output: web + - title: Retrieve a single Property + url: /protocol-examples-retrieveproperty.html + output: web + - title: Create desired Feature Properties + url: /protocol-examples-createdesiredproperties.html + output: web + - title: Delete desired Feature Properties + url: /protocol-examples-deletedesiredproperties.html + output: web + - title: Modify desired Feature Properties + url: /protocol-examples-modifydesiredproperties.html + output: web + - title: Retrieve desired Feature Properties + url: /protocol-examples-retrievedesiredproperties.html + output: web + - title: Create a single desired Property + url: /protocol-examples-createdesiredproperty.html + output: web + - title: Delete a single desired Property + url: /protocol-examples-deletedesiredproperty.html + output: web + - title: Modify a single desired Property + url: /protocol-examples-modifydesiredproperty.html + output: web + - title: Retrieve a single desired Property + url: /protocol-examples-retrievedesiredproperty.html + output: web + - title: Error responses + url: /protocol-examples-errorresponses.html + output: web + + - title: Merge Examples + output: web + subfolderitems: + - title: Merge a Thing + url: /protocol-examples-mergething.html + output: web + - title: Merge Policy ID + url: /protocol-examples-mergepolicyid.html + output: web + - title: Merge Attributes + url: /protocol-examples-mergeattributes.html + output: web + - title: Merge a single Attribute + url: /protocol-examples-mergeattribute.html + output: web + - title: Merge a Definition + url: /protocol-examples-mergethingdefinition.html + output: web + - title: Merge Features + url: /protocol-examples-mergefeatures.html + output: web + - title: Merge a single Feature + url: /protocol-examples-mergefeature.html + output: web + - title: Merge Feature Definition + url: /protocol-examples-mergefeaturedefinition.html + output: web + - title: Merge Feature Properties + url: /protocol-examples-mergeproperties.html + output: web + - title: Merge a single Property + url: /protocol-examples-mergeproperty.html + output: web + - title: Merge desired Feature Properties + url: /protocol-examples-mergedesiredproperties.html + output: web + - title: Merge a single desired Property + url: /protocol-examples-mergedesiredproperty.html + output: web + - title: Error responses + url: /protocol-examples-errorresponses.html + output: web + + - title: Policies Examples + output: web + subfolderitems: + - title: Create a Policy + url: /protocol-examples-policies-createpolicy.html + output: web + - title: Delete a Policy + url: /protocol-examples-policies-deletepolicy.html + output: web + - title: Modify a Policy + url: /protocol-examples-policies-modifypolicy.html + output: web + - title: Retrieve a Policy + url: /protocol-examples-policies-retrievepolicy.html + output: web + - title: Modify entries + url: /protocol-examples-policies-modifypolicyentries.html + output: web + - title: Retrieve entries + url: /protocol-examples-policies-retrievepolicyentries.html + output: web + - title: Create a single entry + url: /protocol-examples-policies-createpolicyentry.html + output: web + - title: Delete a single entry + url: /protocol-examples-policies-deletepolicyentry.html + output: web + - title: Modify a single entry + url: /protocol-examples-policies-modifypolicyentry.html + output: web + - title: Retrieve a single entry + url: /protocol-examples-policies-retrievepolicyentry.html + output: web + - title: Modify subjects + url: /protocol-examples-policies-modifysubjects.html + output: web + - title: Retrieve subjects + url: /protocol-examples-policies-retrievesubjects.html + output: web + - title: Create a single subject + url: /protocol-examples-policies-createsubject.html + output: web + - title: Delete a single subject + url: /protocol-examples-policies-deletesubject.html + output: web + - title: Modify a single subject + url: /protocol-examples-policies-modifysubject.html + output: web + - title: Retrieve a single subject + url: /protocol-examples-policies-retrievesubject.html + output: web + - title: Modify resources + url: /protocol-examples-policies-modifyresources.html + output: web + - title: Retrieve resources + url: /protocol-examples-policies-retrieveresources.html + output: web + - title: Create a single resource + url: /protocol-examples-policies-createresource.html + output: web + - title: Delete a single resource + url: /protocol-examples-policies-deleteresource.html + output: web + - title: Modify a single resource + url: /protocol-examples-policies-modifyresource.html + output: web + - title: Retrieve a single resource + url: /protocol-examples-policies-retrieveresource.html + output: web + - title: Modify policy imports + url: /protocol-examples-policies-modifyimports.html + output: web + - title: Retrieve policy imports + url: /protocol-examples-policies-retrieveimports.html + output: web + - title: Delete a single policy import + url: /protocol-examples-policies-deleteimport.html + output: web + - title: Modify a single policy import + url: /protocol-examples-policies-modifyimport.html + output: web + - title: Retrieve a single policy import + url: /protocol-examples-policies-retrieveimport.html + output: web + - title: Error responses + url: /protocol-examples-policies-errorresponses.html + output: web + - title: Announcement for subject deletion + url: /protocol-examples-policies-announcement-subjectDeletion.html + output: web + + - title: Connections & Search Examples + output: web + subfolderitems: + - title: Connection opened announcement + url: /protocol-examples-connections-announcement-opened.html + output: web + - title: Connection closed announcement + url: /protocol-examples-connections-announcement-closed.html + output: web + - title: Search examples + url: /protocol-examples-search.html + output: web + + - title: Release Notes + output: web + folderitems: + - title: 3.8.12 + url: /release_notes_3812.html + output: web + - title: 3.8.11 + url: /release_notes_3811.html + output: web + - title: 3.8.10 + url: /release_notes_3810.html + output: web + - title: 3.8.9 + url: /release_notes_389.html + output: web + - title: 3.8.8 + url: /release_notes_388.html + output: web + - title: 3.8.7 + url: /release_notes_387.html + output: web + - title: 3.8.6 + url: /release_notes_386.html + output: web + - title: 3.8.5 + url: /release_notes_385.html + output: web + - title: 3.8.4 + url: /release_notes_384.html + output: web + - title: 3.8.3 + url: /release_notes_383.html + output: web + - title: 3.8.2 + url: /release_notes_382.html + output: web + - title: 3.8.1 + url: /release_notes_381.html + output: web + - title: 3.8.0 + url: /release_notes_380.html + output: web + - title: 3.7.6 + url: /release_notes_376.html + output: web + - title: 3.7.5 + url: /release_notes_375.html + output: web + - title: 3.7.4 + url: /release_notes_374.html + output: web + - title: 3.7.3 + url: /release_notes_373.html + output: web + - title: 3.7.2 + url: /release_notes_372.html + output: web + - title: 3.7.1 + url: /release_notes_371.html + output: web + - title: 3.7.0 + url: /release_notes_370.html + output: web + + subfolders: + - title: Archive + output: web + subfolderitems: + - title: 3.6.11 + url: /release_notes_3611.html + output: web + - title: 3.6.10 + url: /release_notes_3610.html + output: web + - title: 3.6.9 + url: /release_notes_369.html + output: web + - title: 3.6.8 + url: /release_notes_368.html + output: web + - title: 3.6.7 + url: /release_notes_367.html + output: web + - title: 3.6.5 + url: /release_notes_365.html + output: web + - title: 3.6.4 + url: /release_notes_364.html + output: web + - title: 3.6.3 + url: /release_notes_363.html + output: web + - title: 3.6.2 + url: /release_notes_362.html + output: web + - title: 3.6.1 + url: /release_notes_361.html + output: web + - title: 3.6.0 + url: /release_notes_360.html + output: web + - title: 3.5.12 + url: /release_notes_3512.html + output: web + - title: 3.5.11 + url: /release_notes_3511.html + output: web + - title: 3.5.10 + url: /release_notes_3510.html + output: web + - title: 3.5.9 + url: /release_notes_359.html + output: web + - title: 3.5.8 + url: /release_notes_358.html + output: web + - title: 3.5.7 + url: /release_notes_357.html + output: web + - title: 3.5.6 + url: /release_notes_356.html + output: web + - title: 3.5.5 + url: /release_notes_355.html + output: web + - title: 3.5.4 + url: /release_notes_354.html + output: web + - title: 3.5.3 + url: /release_notes_353.html + output: web + - title: 3.5.2 + url: /release_notes_352.html + output: web + - title: 3.5.1 + url: /release_notes_351.html + output: web + - title: 3.5.0 + url: /release_notes_350.html + output: web + - title: 3.4.5 + url: /release_notes_345.html output: web - - title: Modify Feature Properties - url: /protocol-examples-modifyproperties.html + - title: 3.4.4 + url: /release_notes_344.html output: web - - title: Retrieve Feature Properties - url: /protocol-examples-retrieveproperties.html + - title: 3.4.3 + url: /release_notes_343.html output: web - - title: Create a single Property - url: /protocol-examples-createproperty.html + - title: 3.4.2 + url: /release_notes_342.html output: web - - title: Delete a single Property - url: /protocol-examples-deleteproperty.html + - title: 3.4.1 + url: /release_notes_341.html output: web - - title: Modify a single Property - url: /protocol-examples-modifyproperty.html + - title: 3.4.0 + url: /release_notes_340.html output: web - - title: Retrieve a single Property - url: /protocol-examples-retrieveproperty.html + - title: 3.3.7 + url: /release_notes_337.html output: web - - title: Create desired Feature Properties - url: /protocol-examples-createdesiredproperties.html + - title: 3.3.6 + url: /release_notes_336.html output: web - - title: Delete desired Feature Properties - url: /protocol-examples-deletedesiredproperties.html + - title: 3.3.5 + url: /release_notes_335.html output: web - - title: Modify desired Feature Properties - url: /protocol-examples-modifydesiredproperties.html + - title: 3.3.4 + url: /release_notes_334.html output: web - - title: Retrieve desired Feature Properties - url: /protocol-examples-retrievedesiredproperties.html + - title: 3.3.3 + url: /release_notes_333.html output: web - - title: Create a single desired Property - url: /protocol-examples-createdesiredproperty.html + - title: 3.3.2 + url: /release_notes_332.html output: web - - title: Delete a single desired Property - url: /protocol-examples-deletedesiredproperty.html + - title: 3.3.0 + url: /release_notes_330.html output: web - - title: Modify a single desired Property - url: /protocol-examples-modifydesiredproperty.html + - title: 3.2.1 + url: /release_notes_321.html output: web - - title: Retrieve a single desired Property - url: /protocol-examples-retrievedesiredproperty.html + - title: 3.2.0 + url: /release_notes_320.html output: web - - title: Error responses - url: /protocol-examples-errorresponses.html + - title: 3.1.2 + url: /release_notes_312.html output: web - - title: → Things merge examples - output: web - subfolderitems: - - title: Merge a Thing - url: /protocol-examples-mergething.html + - title: 3.1.1 + url: /release_notes_311.html output: web - - title: Merge the Policy ID of a Thing - url: /protocol-examples-mergepolicyid.html + - title: 3.1.0 + url: /release_notes_310.html output: web - - title: Merge Attributes - url: /protocol-examples-mergeattributes.html + - title: 3.0.0 + url: /release_notes_300.html output: web - - title: Merge a single Attribute - url: /protocol-examples-mergeattribute.html + - title: 2.4.2 + url: /release_notes_242.html output: web - - title: Merge a Definition - url: /protocol-examples-mergethingdefinition.html + - title: 2.4.1 + url: /release_notes_241.html output: web - - title: Merge Features - url: /protocol-examples-mergefeatures.html + - title: 2.4.0 + url: /release_notes_240.html output: web - - title: Merge a single Feature - url: /protocol-examples-mergefeature.html + - title: 2.3.2 + url: /release_notes_232.html output: web - - title: Merge Feature Definition - url: /protocol-examples-mergefeaturedefinition.html + - title: 2.3.1 + url: /release_notes_231.html output: web - - title: Merge Feature Properties - url: /protocol-examples-mergeproperties.html + - title: 2.3.0 + url: /release_notes_230.html output: web - - title: Merge a single Property - url: /protocol-examples-mergeproperty.html + - title: 2.2.2 + url: /release_notes_222.html output: web - - title: Merge desired Feature Properties - url: /protocol-examples-mergedesiredproperties.html + - title: 2.2.1 + url: /release_notes_221.html output: web - - title: Merge a single desired Property - url: /protocol-examples-mergedesiredproperty.html + - title: 2.2.0 + url: /release_notes_220.html output: web - - title: Error responses - url: /protocol-examples-errorresponses.html + - title: 2.1.3 + url: /release_notes_213.html output: web - - title: → Policies examples - output: web - subfolderitems: - - title: Create a Policy - url: /protocol-examples-policies-createpolicy.html + - title: 2.1.2 + url: /release_notes_212.html output: web - - title: Delete a Policy - url: /protocol-examples-policies-deletepolicy.html + - title: 2.1.1 + url: /release_notes_211.html output: web - - title: Modify a Policy - url: /protocol-examples-policies-modifypolicy.html + - title: 2.1.0 + url: /release_notes_210.html output: web - - title: Retrieve a Policy - url: /protocol-examples-policies-retrievepolicy.html + - title: 2.0.1 + url: /release_notes_201.html output: web - - title: Modify entries - url: /protocol-examples-policies-modifypolicyentries.html + - title: 2.0.0 + url: /release_notes_200.html output: web - - title: Retrieve entries - url: /protocol-examples-policies-retrievepolicyentries.html + - title: 1.5.1 + url: /release_notes_151.html output: web - - title: Create a single entry - url: /protocol-examples-policies-createpolicyentry.html + - title: 1.5.0 + url: /release_notes_150.html output: web - - title: Delete a single entry - url: /protocol-examples-policies-deletepolicyentry.html + - title: 1.4.0 + url: /release_notes_140.html output: web - - title: Modify a single entry - url: /protocol-examples-policies-modifypolicyentry.html + - title: 1.3.0 + url: /release_notes_130.html output: web - - title: Retrieve a single entry - url: /protocol-examples-policies-retrievepolicyentry.html + - title: 1.2.1 + url: /release_notes_121.html output: web - - title: Modify subjects - url: /protocol-examples-policies-modifysubjects.html + - title: 1.2.0 + url: /release_notes_120.html output: web - - title: Retrieve subjects - url: /protocol-examples-policies-retrievesubjects.html + - title: 1.1.5 + url: /release_notes_115.html output: web - - title: Create a single subject - url: /protocol-examples-policies-createsubject.html + - title: 1.1.3 + url: /release_notes_113.html output: web - - title: Delete a single subject - url: /protocol-examples-policies-deletesubject.html + - title: 1.1.2 + url: /release_notes_112.html output: web - - title: Modify a single subject - url: /protocol-examples-policies-modifysubject.html + - title: 1.1.1 + url: /release_notes_111.html output: web - - title: Retrieve a single subject - url: /protocol-examples-policies-retrievesubject.html + - title: 1.1.0 + url: /release_notes_110.html output: web - - title: Modify resources - url: /protocol-examples-policies-modifyresources.html + - title: 1.0.0 + url: /release_notes_100.html output: web - - title: Retrieve resources - url: /protocol-examples-policies-retrieveresources.html + - title: 0.9.0 + url: /release_notes_090.html output: web - - title: Create a single resource - url: /protocol-examples-policies-createresource.html + - title: 0.8.0 + url: /release_notes_080.html output: web - - title: Delete a single resource - url: /protocol-examples-policies-deleteresource.html + - title: 1.0.0-M2 + url: /release_notes_100-M2.html output: web - - title: Modify a single resource - url: /protocol-examples-policies-modifyresource.html + - title: 1.0.0-M1a + url: /release_notes_100-M1a.html output: web - - title: Retrieve a single resource - url: /protocol-examples-policies-retrieveresource.html + - title: 0.9.0-M2 + url: /release_notes_090-M2.html output: web - - title: Modify policy imports - url: /protocol-examples-policies-modifyimports.html + - title: 0.9.0-M1 + url: /release_notes_090-M1.html output: web - - title: Retrieve policy imports - url: /protocol-examples-policies-retrieveimports.html + - title: 0.8.0-M3 + url: /release_notes_080-M3.html output: web - - title: Delete a single policy import - url: /protocol-examples-policies-deleteimport.html + - title: 0.8.0-M2 + url: /release_notes_080-M2.html output: web - - title: Modify a single policy import - url: /protocol-examples-policies-modifyimport.html + - title: 0.8.0-M1 + url: /release_notes_080-M1.html output: web - - title: Retrieve a single policy import - url: /protocol-examples-policies-retrieveimport.html + - title: 0.3.0-M2 + url: /release_notes_030-M2.html output: web - - title: Error responses - url: /protocol-examples-policies-errorresponses.html + - title: 0.3.0-M1 + url: /release_notes_030-M1.html output: web - - title: Announcement for subject deletion - url: /protocol-examples-policies-announcement-subjectDeletion.html + - title: 0.2.0-M1 + url: /release_notes_020-M1.html output: web - - title: → Connections examples - output: web - subfolderitems: - - title: Announcement for connection opened - url: /protocol-examples-connections-announcement-opened.html + - title: 0.1.0-M3 + url: /release_notes_010-M3.html output: web - - title: Announcement for connection gracefully closed - url: /protocol-examples-connections-announcement-closed.html + - title: 0.1.0-M1 + url: /release_notes_010-M1.html output: web - - title: → Search examples - url: /protocol-examples-search.html - output: web - - - title: User Interface - url: /user-interface.html - output: web - - title: Presentations - url: /presentations.html - output: web - - - title: Glossary - url: /glossary.html - output: web - - - title: Feedback - url: /feedback.html + - title: Resources output: web + folderitems: + - title: Glossary + url: /glossary.html + output: web + - title: Presentations & Talks + url: /presentations.html + output: web + - title: Feedback & Community + url: /feedback.html + output: web diff --git a/documentation/src/main/resources/_includes/topnav.html b/documentation/src/main/resources/_includes/topnav.html index d17e798b338..7c30996ced2 100644 --- a/documentation/src/main/resources/_includes/topnav.html +++ b/documentation/src/main/resources/_includes/topnav.html @@ -68,21 +68,37 @@
  • -
    - - -
    - - +
  • diff --git a/documentation/src/main/resources/_layouts/default.html b/documentation/src/main/resources/_layouts/default.html index 985d58f0c42..20807fe536f 100644 --- a/documentation/src/main/resources/_layouts/default.html +++ b/documentation/src/main/resources/_layouts/default.html @@ -34,7 +34,7 @@ {% endunless %} -
    +
    {{content}}
    diff --git a/documentation/src/main/resources/_posts/2018-02-07-milestone-announcement-020-M1.md b/documentation/src/main/resources/_posts/2018-02-07-milestone-announcement-020-M1.md index 5b633fe117b..3934374a0e9 100644 --- a/documentation/src/main/resources/_posts/2018-02-07-milestone-announcement-020-M1.md +++ b/documentation/src/main/resources/_posts/2018-02-07-milestone-announcement-020-M1.md @@ -16,7 +16,7 @@ The Ditto team is proud to announce the next milestone release. Have a look at the Milestone [0.2.0-M1 release notes](release_notes_020-M1.html). The main changes are -* being able to [search in namespaces](httpapi-search.html#query-parameters) which can speed up search queries when applied +* being able to [search in namespaces](httpapi-search.html#examples) which can speed up search queries when applied to a large population of digital twins * the enhancement of our [Feature entity](basic-feature.html) by [Definitions](basic-feature.html#feature-definition) which lays the foundation for using Features in a typesafe way (later by enforcing the schema with the help of an diff --git a/documentation/src/main/resources/_posts/2018-12-05-example-command-and-control.md b/documentation/src/main/resources/_posts/2018-12-05-example-command-and-control.md index bd30c0e47df..248a283b953 100644 --- a/documentation/src/main/resources/_posts/2018-12-05-example-command-and-control.md +++ b/documentation/src/main/resources/_posts/2018-12-05-example-command-and-control.md @@ -193,7 +193,7 @@ The request is now open to receive a command for 60 seconds before it is termina ### Send a Ditto message -Now we can use the [Ditto Messages API](protocol-specification-things-messages.html#using-the-messages-api) to send a +Now we can use the [Ditto Messages API](protocol-specification-things-messages.html#examples) to send a message to the device waiting for a command: ```bash curl -i -X POST 'https://ditto.eclipseprojects.io/api/2/things/org.eclipse.ditto:teapot/inbox/messages/brew?timeout=60' \ diff --git a/documentation/src/main/resources/_posts/2021-09-29-kafka-connectivity.md b/documentation/src/main/resources/_posts/2021-09-29-kafka-connectivity.md index ac7b44a4a5a..dc9ec233325 100644 --- a/documentation/src/main/resources/_posts/2021-09-29-kafka-connectivity.md +++ b/documentation/src/main/resources/_posts/2021-09-29-kafka-connectivity.md @@ -18,7 +18,7 @@ The time has come to support consuming as well. A Kafka connection behaves slightly different from other consuming Connections in Ditto. The following aspects are special: * [Backpressure by using acknowledgements](connectivity-protocol-bindings-kafka2.html#quality-of-service) -* [Preserve order of messages on redelivery due to failed acknowledgements](connectivity-protocol-bindings-kafka2.html#backpressure-by-using-acknowledgements) +* [Preserve order of messages on redelivery due to failed acknowledgements](connectivity-protocol-bindings-kafka2.html#backpressure-via-acknowledgements) * [Expiry of messages](connectivity-protocol-bindings-kafka2.html#message-expiry) ### Scalability diff --git a/documentation/src/main/resources/_posts/2021-11-03-oauth2.md b/documentation/src/main/resources/_posts/2021-11-03-oauth2.md index 6ab14f5f88e..465c1423265 100644 --- a/documentation/src/main/resources/_posts/2021-11-03-oauth2.md +++ b/documentation/src/main/resources/_posts/2021-11-03-oauth2.md @@ -82,7 +82,7 @@ placeholder. The unencoded headers and body are as follows. # Create the connection -[Create a connection](connectivity-manage-connections.html#create-connection) +[Create a connection](connectivity-manage-connections.html#create-a-connection) publishing twin events to the event publishing webhook using OAuth2 credentials. The `tokenEndpoint` field is set to the access token webhook. diff --git a/documentation/src/main/resources/_posts/2021-12-20-http-live-channel.md b/documentation/src/main/resources/_posts/2021-12-20-http-live-channel.md index b671610401b..de17d7e82a1 100644 --- a/documentation/src/main/resources/_posts/2021-12-20-http-live-channel.md +++ b/documentation/src/main/resources/_posts/2021-12-20-http-live-channel.md @@ -11,21 +11,21 @@ toc: true --- The upcoming release of Eclipse Ditto **version 2.3.0** will support sending commands via the HTTP API -directly to devices using the [live channel](protocol-twinlive.html#live) by just adding the `channel=live` -query parameter to the same HTTP API request which would target the [twin](protocol-twinlive.html#twin). +directly to devices using the [live channel](protocol-twinlive.html#live-channel) by just adding the `channel=live` +query parameter to the same HTTP API request which would target the [twin](protocol-twinlive.html#twin-channel). ## HTTP Live channel -Ditto supports sending all kind of [Thing commands](protocol-specification-things.html#commands) via +Ditto supports sending all kind of [Thing commands](protocol-specification-things.html#create-and-modify-commands) via the `live` channel directly to devices. When sending a `live` command to a device, the device is responsible for sending a correlated and correct response in [Ditto Protocol](protocol-overview.html). Ditto supports two types of `channel`s for communication. -* [twin](protocol-twinlive.html#twin): The default value of the channel parameter is `twin` +* [twin](protocol-twinlive.html#twin-channel): The default value of the channel parameter is `twin` to communicate with the persisted **twin** representation. -* [live](protocol-twinlive.html#live): The `channel` parameter can be changed to `live` +* [live](protocol-twinlive.html#live-channel): The `channel` parameter can be changed to `live` to communicate with the real device. {% include note.html content="In order to use the live channel, the device receiving live commands must be able to understand diff --git a/documentation/src/main/resources/_posts/2021-12-22-live-channel-condition.md b/documentation/src/main/resources/_posts/2021-12-22-live-channel-condition.md index 508e1e10488..6e5d487a244 100644 --- a/documentation/src/main/resources/_posts/2021-12-22-live-channel-condition.md +++ b/documentation/src/main/resources/_posts/2021-12-22-live-channel-condition.md @@ -10,11 +10,11 @@ sidebar: false toc: true --- -After the added option to target the [live channel](protocol-twinlive.html#live) by adding the `channel=live` to HTTP +After the added option to target the [live channel](protocol-twinlive.html#live-channel) by adding the `channel=live` to HTTP requests (see also [blog post about "HTTP live channel"](2021-12-20-http-live-channel.html)), Eclipse Ditto **version 2.3.0** will in addition support to define a [live channel condition](basic-conditional-requests.html#live-channel-condition), which, when evaluating to `true`, -will retrieve data from a device via the live channel, or fall back to the persisted [twin](protocol-twinlive.html#twin) +will retrieve data from a device via the live channel, or fall back to the persisted [twin](protocol-twinlive.html#twin-channel) otherwise. {% include note.html content="In order to use the live channel, the device receiving live commands must be able to understand @@ -72,7 +72,7 @@ GET /api/2/things/my.namespace:my-polling-device-1/features/temperature/properti ``` The specified `live-channel-condition` will evaluate to `true`, meaning that the retrieve is transformed to a -[live command](protocol-twinlive.html#live) and sent to the device, e.g. connected via a +[live command](protocol-twinlive.html#live-channel) and sent to the device, e.g. connected via a [managed connection](basic-connections.html). Upon receiving the "live retrieve" at the device, the device can create a command response correlated with the same `correlation-id` and send it back to Ditto with the current value. @@ -80,7 +80,7 @@ This value is then returned as result of the `GET`, the HTTP response header `ch sent by the device by having the value `live`. If the device does not answer with a correctly correlated response within the given `timeout`, the request will fall back -to the [twin](protocol-twinlive.html#twin) channel, retrieving the data from the last known persisted temperature value +to the [twin](protocol-twinlive.html#twin-channel) channel, retrieving the data from the last known persisted temperature value in the twin managed by Ditto. The HTTP response header `channel` will indicate that the data was received by the persisted twin by having the value `twin`. diff --git a/documentation/src/main/resources/_posts/2022-01-21-release-announcement-230.md b/documentation/src/main/resources/_posts/2022-01-21-release-announcement-230.md index 6a958137555..a6b5e036f84 100644 --- a/documentation/src/main/resources/_posts/2022-01-21-release-announcement-230.md +++ b/documentation/src/main/resources/_posts/2022-01-21-release-announcement-230.md @@ -12,11 +12,11 @@ toc: false The Eclipse Ditto teams announces availability of Eclipse Ditto [2.3.0](https://projects.eclipse.org/projects/iot.ditto/releases/2.3.0). -It contains mainly new features around the Ditto ["live" channel](protocol-twinlive.html#live) which can be used to +It contains mainly new features around the Ditto ["live" channel](protocol-twinlive.html#live-channel) which can be used to directly interact with devices. Such live commands may now be easily [created via the HTTP API](2021-12-20-http-live-channel.html) - and in addition -a conventional API call targeting the persisted [twin](protocol-twinlive.html#twin) may now be automatically converted +a conventional API call targeting the persisted [twin](protocol-twinlive.html#twin-channel) may now be automatically converted to a live command, based on a passed [live channel condition](2021-12-22-live-channel-condition.html). With that, we are proud that we now can provide a really powerful addition to Ditto's "Digital Twin pattern": diff --git a/documentation/src/main/resources/_posts/2023-10-09-ditto-benchmark.md b/documentation/src/main/resources/_posts/2023-10-09-ditto-benchmark.md index d66b930b612..867b54943ec 100644 --- a/documentation/src/main/resources/_posts/2023-10-09-ditto-benchmark.md +++ b/documentation/src/main/resources/_posts/2023-10-09-ditto-benchmark.md @@ -220,7 +220,7 @@ Example message: device_id:org.eclipse.ditto:test-thing-1,ditto_message:y!{"topic":"org.eclipse.ditto/test-thing-1/things/twin/commands/modify","path":"features/coffee-brewer/properties/brewed-coffees","value":"10"} ``` -In this scenario, connectivity service is used to create a ditto kafka connection, which reads messages from the provided topic, maps them to a ditto modify command and forwards it to things service. The things service then executes mongodb update query and generates the [thing modified event](protocol-specification-things-create-or-modify.html#event), which is pushed to the MMock service instance via an HTTP Push connection. Also, the kafka connection is configured with [qos=1](connectivity-protocol-bindings-kafka2.html#quality-of-service), which means if there is no acknowledgement that the thing is persisted, the operation will be retried. +In this scenario, connectivity service is used to create a ditto kafka connection, which reads messages from the provided topic, maps them to a ditto modify command and forwards it to things service. The things service then executes mongodb update query and generates the [thing modified event](protocol-specification-things.html#create-or-modify-a-thing), which is pushed to the MMock service instance via an HTTP Push connection. Also, the kafka connection is configured with [qos=1](connectivity-protocol-bindings-kafka2.html#quality-of-service), which means if there is no acknowledgement that the thing is persisted, the operation will be retried. The HTTP Push connection looks like the following: @@ -354,7 +354,7 @@ Scaling connectivity instance and changing our connection to have **clientCount* ### Device live messages(commands) -This scenario executes HTTP POST requests to ditto's [live channel](protocol-twinlive.html#live). An HTTP Push connection is subscribed for them and in turn pushes to a MMock instance that acts as a 'dummy' device receiver of live messages/commands and simply responds with pre-configured ditto response. +This scenario executes HTTP POST requests to ditto's [live channel](protocol-twinlive.html#live-channel). An HTTP Push connection is subscribed for them and in turn pushes to a MMock instance that acts as a 'dummy' device receiver of live messages/commands and simply responds with pre-configured ditto response. The HTTP POST request looks like the following: diff --git a/documentation/src/main/resources/css/customstyles.css b/documentation/src/main/resources/css/customstyles.css index 24542d119eb..c69d1b7ce09 100644 --- a/documentation/src/main/resources/css/customstyles.css +++ b/documentation/src/main/resources/css/customstyles.css @@ -297,43 +297,68 @@ p.post-meta {font-size: 80%; color: #777;} .entry-date{font-size:14px; line-height:1.71429; margin-bottom:0; text-transform:uppercase;} -/* search area */ -#search-demo-container ul#results-container { - list-style: none; - font-size: 12px; - background-color: white; - position: absolute; - top: 40px; /* if you change anything about the nav, you'll prob. need to reset the top and left values here.*/ - left: 20px; - z-index: -1; - width:223px; - border-left: 1px solid #dedede; +/* pagefind search overrides */ +.pagefind-search-container { + position: relative; + margin-top: 7px; + margin-left: 10px; } +.pagefind-search-container .pagefind-ui { + --pagefind-ui-scale: 0.6; + --pagefind-ui-primary: #3a8c9a; +} -ul#results-container a { - background-color: transparent; +.pagefind-search-container .pagefind-ui__form { + padding: 0; + margin: 0; } -ul#results-container a:hover { - color: black; +.pagefind-search-container .pagefind-ui__search-input { + padding: 2px 8px; + width: 12em; + font-size: 13px; + font-family: 'Open Sans', sans-serif; + box-sizing: border-box; + height: 24px; + border-radius: 3px; +} + +.pagefind-search-container .pagefind-ui__search-clear { + top: auto; + height: 24px; + padding: 0 4px; } +.pagefind-search-container .pagefind-ui__drawer { + position: absolute; + right: 0; + top: 100%; + z-index: 10000; + background: white; + width: 400px; + max-height: 70vh; + overflow-y: auto; + border: 1px solid #dedede; + border-radius: 0 0 4px 4px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} -#search-demo-container a:hover { - color: black; +.pagefind-search-container .pagefind-ui__result-link { + color: #3a8c9a; } -#search-input { - padding: .5em; - margin-left:15px; - width:15em; - font-size: 0.8em; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - margin-top:10px; + +.pagefind-search-container .pagefind-ui__result-link:hover { + color: #248ec2; +} + +@media (max-width: 768px) { + .pagefind-search-container .pagefind-ui__drawer { + width: 90vw; + right: -50px; + } } -/* end search */ +/* end pagefind search */ .filter-options { margin-bottom: 20px; diff --git a/documentation/src/main/resources/openapi/ditto-api-2.yml b/documentation/src/main/resources/openapi/ditto-api-2.yml index 3d7b9e27312..9ce1b899d8d 100644 --- a/documentation/src/main/resources/openapi/ditto-api-2.yml +++ b/documentation/src/main/resources/openapi/ditto-api-2.yml @@ -4238,7 +4238,7 @@ paths: No special permission is required to issue a claim message. ### Example - See [Claiming](https://www.eclipse.dev/ditto/protocol-specification-things-messages.html#sending-and-handling-claim-messages) concept in detail and example in GitHub. + See [Claiming](https://www.eclipse.dev/ditto/protocol-specification-things-messages.html#claim-messages) concept in detail and example in GitHub. However, in that scenario, the policy should grant you READ and WRITE permission on the "message:/" resource in order to be able to send the message and read the response. Further, the things-client which handles the "claim" message, needs permission to change the policy itself diff --git a/documentation/src/main/resources/openapi/sources/paths/messages/inbox-claim.yml b/documentation/src/main/resources/openapi/sources/paths/messages/inbox-claim.yml index bdbd4c7d102..f2b6149cb7c 100644 --- a/documentation/src/main/resources/openapi/sources/paths/messages/inbox-claim.yml +++ b/documentation/src/main/resources/openapi/sources/paths/messages/inbox-claim.yml @@ -38,7 +38,7 @@ post: No special permission is required to issue a claim message. ### Example - See [Claiming](https://www.eclipse.dev/ditto/protocol-specification-things-messages.html#sending-and-handling-claim-messages) concept in detail and example in GitHub. + See [Claiming](https://www.eclipse.dev/ditto/protocol-specification-things-messages.html#claim-messages) concept in detail and example in GitHub. However, in that scenario, the policy should grant you READ and WRITE permission on the "message:/" resource in order to be able to send the message and read the response. Further, the things-client which handles the "claim" message, needs permission to change the policy itself diff --git a/documentation/src/main/resources/pages/ditto/advanced-data-by-pass.md b/documentation/src/main/resources/pages/ditto/advanced-data-by-pass.md index ca68151735f..efab018d299 100644 --- a/documentation/src/main/resources/pages/ditto/advanced-data-by-pass.md +++ b/documentation/src/main/resources/pages/ditto/advanced-data-by-pass.md @@ -5,84 +5,51 @@ tags: [advanced] permalink: advanced-data-by-pass.html --- -This pattern centers around the idea to delegate the data transmission to external services, by-passing the Ditto cluster, -while still being able to benefit from Ditto's [policy system](basic-policy.html) and IoT architecture. +The Data By-Pass pattern delegates large data transfers to an external high-performance proxy while still using Ditto's [policy system](basic-policy.html) for access control. -## Context +{% include callout.html content="**TL;DR**: When you need to transfer data volumes too large for the Ditto cluster (e.g., database query results), route the negotiation through Ditto messages for authentication, then redirect the client to a high-performance proxy for the actual data retrieval." type="primary" %} -You have services exposing their functionality transparently though Ditto's messaging API as part of your digital twin. -E.g. a history service providing the actual interface to your timeseries database as part of the things interface such -that a client may not need to know if the history actually is managed by the thing itself or any other program. -You use Ditto's [policy system](basic-policy.html) to secure access to your services that way. +## Overview -Your services provide data in quantities that are not suited for transmission through the Ditto cluster directly, -because of (de-)serialization costs, round-trip-times etc. +You may have services that expose functionality through Ditto's messaging API as part of your digital twin -- for example, a history service providing access to a time-series database through a Thing's interface. You use Ditto's [policy system](basic-policy.html) to secure access to these services. -## Problem +However, some data volumes are too large for the Ditto cluster. Serialization costs, roundtrip times, and message size quotas make it impractical to return large query results as Ditto message responses. -You want to query a greater amount of data (e.g. database query result) by issuing a Ditto message to a thing which is -picked up by a service speaking with you databases. It does not work to just let this service return the result -as a response to the Ditto message, since the messaging system in the Ditto cluster is not designed for big quantities -of data and will reject them based on tight quotas. Also the costs due to many (de-)serialization steps are high. +## How it works -## Solution +### Architecture -The solution consists of the following systems: +The pattern involves five components: -* **database**: where your bigger chunks of data reside and wait to be delivered / queried -* **database provider mirco-service**: the service managing the database connection and exposing it to clients through - things messaging API -* **thing**: a digital twin with extended API through a micro-service -* **client**: a client-application trying to receive bigger quantities of data via a things messaging API in the scope - of that thing and secured via ditto policies -* **high-performance data proxy** (or just proxy): a third-party application proxy sitting in-between the database and - the provider micro-service managing data delivery +* **Database**: Where your larger data sets reside +* **Database provider microservice**: Manages the database connection and exposes it through the Thing messaging API +* **Thing**: A digital twin with an extended API through the microservice +* **Client**: An application requesting data via the Thing messaging API +* **High-performance data proxy**: A third-party proxy (e.g., nginx-based) that sits between the database and the client for actual data delivery -{% include image.html file="pages/advanced/data-by-pass-architectural-design.jpg" alt="Architectural Design" -caption="Architectural design of the data by-pass pattern showing all actors and their interactions." max-width=800 %} +{% include image.html file="pages/advanced/data-by-pass-architectural-design.jpg" alt="Architectural Design" caption="Architectural design of the data by-pass pattern showing all actors and their interactions." max-width=800 %} -In order for the client-application to retrieve the requested data in a secure and performant way we introduce a -high-performance proxy (e.g. based on nginx, example below). The proxy will not have any credentials by itself, -it's just serving prepared queries on a randomly, hard-to-guess URL with an expiration time of 5 minutes. -It features an admin API which the micro-service has credentials to access. +### Request flow -The provider micro-service hooks into a twin (e.g. via websockets) and listens for queries. -If a query arrives it will formulate the query, store it at the high-performance proxy (which might already query the data) -and return a randomly generated URL to the proxy together with a Location-header as a response to the client-application. -The client then needs to follow the response in order to retrieve the data from the proxy. +1. The client sends a query as a Ditto message to a Thing. +2. The provider microservice (listening via WebSocket) receives the message. +3. The microservice formulates the database query and stores it at the high-performance proxy under a randomly generated, hard-to-guess URL with a 5-minute expiration. +4. The microservice returns the proxy URL with a `Location` header (HTTP 303) as the Ditto message response. +5. The client follows the redirect to retrieve the actual data from the proxy. -With this approach the access to the database is secured via Ditto [policies](basic-policy.html) and in scope of single -things while the data retrieval happens via a performant proxy application without the Ditto cluster ever seeing those packages. +The proxy itself has no credentials -- it only serves pre-prepared queries on expiring random URLs. The provider microservice holds the admin credentials for the proxy's configuration API. -*Note: Keep in mind that security in this situation is highly dependent of the micro-service implementation. -You have to make sure that your implementation uses provided information of ditto properly and that the contents of -a message do not allow a violation of the policy. E.g. through SQL-Injections.* +### Security -## Discussion - -**Benefits**: - -* Higher performance compared to using just Ditto -* The Ditto [policy system](basic-policy.html) can be utilized to scope and secure data access from clients to databases/-stores - -**Drawbacks**: - -* A third-party application for the high-performance proxy has to be added and maintained -* A custom messaging API is necessary in the first place introducing a higher complexity -* A translation of certain query-languages from messages to the actual databases / applications has to be implemented - -**Issues**: +Access to the database is secured through Ditto [policies](basic-policy.html) and scoped to individual Things. The actual data transfer bypasses the Ditto cluster entirely. -* Managing and communicating custom messaging APIs is not natively supported in Ditto, other ways have to be explored to - keep APIs consistent +{% include warning.html content="Security depends heavily on your microservice implementation. Ensure that message contents cannot violate policy boundaries, for example through SQL injection attacks." %} -## Policies +## Configuration -Policies can be used to restrict access to the provider micro-service and through that eventually to the database using -restrictions on the `message:/` resource. +### Policy setup -Let's assume that the provider micro-service registers via websockets and expects requests to the message-topic -`/services/history`. With the following policy entry we can allow access to this resource: +Restrict access to the provider microservice using `message:/` resources. If the microservice listens for messages on the topic `/services/history`: ```json { @@ -104,34 +71,36 @@ Let's assume that the provider micro-service registers via websockets and expect } ``` -The first resource entry revokes any access to messages for subjects of this type. This is optional. The next entry -allows the provider micro-service to read messages from the topic `/services/history`. Note that we've decided to insert -another "namespace" `/services` here to distinguish these messages from other device faced messages. The last section -than allows the provider micro-service to reply to the received requests with it's 303 response. +The first entry revokes all message access for subjects of this type (optional). The second entry allows the microservice to read incoming queries. The third entry allows the microservice to send responses. -This can also be built against single features. Since features have to be stated explicitly in the policy, this is not -as general but can provide a more fine-grained access control when using distinct policies for different things, -or features with same names over multiple things. +You can also scope this to individual Features for more fine-grained access control. -## Proxy Implementations +## Examples + +### Proxy implementation + +The [ceryx proxy project](https://github.com/sourcelair/ceryx) was used for the [reference implementation](https://github.com/w4tsn/ceryx). It was enhanced with delegation features (a modified nginx with Redis for storing randomly generated IDs correlated with prepared queries and authentication). See the [fork's source code](https://github.com/w4tsn/ceryx) or the [container image](https://quay.io/repository/w4tsn/ceryx). + +### Known uses + +**[othermo GmbH](https://www.othermo.de)** uses this pattern for a history service. The service connects to Ditto via WebSocket and responds to `/history` messages. It translates the messaging API into InfluxDB queries, stores them at the proxy with a 5-minute expiring URL, and returns the URL to the client. The provider service uses InfluxDB tags to scope data by `thingId`, policy, and path information. + +## Discussion -The [ceryx proxy project](https://github.com/sourcelair/ceryx) was used for the -[PoC (or reference implementation)](https://github.com/w4tsn/ceryx) of this pattern. -It was enhanced with delegation features which still have to be contributed upstream. -Have a look at the [forks source code](https://github.com/w4tsn/ceryx) or -the [corresponding container image](https://quay.io/repository/w4tsn/ceryx) until then. +**Benefits:** +* Higher data throughput compared to routing everything through Ditto +* Ditto's policy system secures and scopes data access from clients to databases -The ceryx proxy is a modified nginx with a redis-database to store the randomly generated IDs correlating with prepared queries. -It is not suited for this use-case on its own so capabilities to store queries (including Authentication) behind -expiring random URLs was added, but not send upstream yet. +**Drawbacks:** +* You must add and maintain a third-party proxy application +* You need a custom messaging API, which adds complexity +* You must implement query language translation from messages to your database -## Known uses +**Open issues:** +* Ditto does not natively support managing and communicating custom messaging APIs -**[othermo GmbH](https://www.othermo.de) uses this for a history-service**: The history service connects to Ditto via -websockets and hooks into things by answering specific `/history` messages. The messages API is translated to InfluxDB queries -which then are stored with a randomly generated URL and expiration of 5 minutes at the high-performance proxy. -The service then returns the random URL to the client which then follows the 303 to retrieve the actual data. +## Further reading -The messages contain InfluxDB-similar query elements while the query is only constructed at the provider service. -That's because the provider service uses the databases specifics like Tags in InfluxDB to assign thingId, policy and -path information in order to get the stored data into the right scopes and to be able to retrieve the correct sets of data. +* [Policies](basic-policy.html) +* [Messages](basic-messages.html) +* [WebSocket Protocol Binding](httpapi-protocol-bindings-websocket.html) diff --git a/documentation/src/main/resources/pages/ditto/architecture-overview.md b/documentation/src/main/resources/pages/ditto/architecture-overview.md index 1bcc9aa3775..1d90b493d29 100644 --- a/documentation/src/main/resources/pages/ditto/architecture-overview.md +++ b/documentation/src/main/resources/pages/ditto/architecture-overview.md @@ -5,47 +5,50 @@ tags: [architecture] permalink: architecture-overview.html --- -The architecture chapter describes the overall architecture of Eclipse Ditto and in detail which sub-components fulfill -which responsibilities. +Eclipse Ditto consists of five microservices that communicate over a Pekko cluster to provide digital twin management, access control, search, and connectivity to external systems. -## Top level component view +{% include callout.html content="**TL;DR**: Ditto runs five services -- Policies, Things, Things-Search, Gateway, and Connectivity -- all in one Pekko cluster. They communicate via TCP using serializable signals, with MongoDB as the persistence layer and nginx as a reverse proxy." type="primary" %} -This overview shows the Ditto services (components), the externally provided and consumed API endpoints, -the external dependencies (MongoDB and nginx) and the relations of the services to each other. +## Overview + +The architecture of Eclipse Ditto is built around five cooperating microservices, each with a clear responsibility. This page describes the top-level component view, how services are defined, and how they communicate. + +## How it works + +### Component view {% include image.html file="pages/architecture/ditto-architecture-overview-2022.png" alt="Ditto services and context" caption="Ditto services in blue and context with nginx as reverse proxy and MongoDB" max-width=800 %} -The components have the following tasks: +The five services and their responsibilities: + +| Service | Responsibilities | +|---------|-----------------| +| [Policies](architecture-services-policies.html) | Persist and enforce (authorize) [Policies](basic-policy.html) | +| [Things](architecture-services-things.html) | Persist and enforce (authorize) [Things](basic-thing.html) and [Features](basic-feature.html) | +| [Things-Search](architecture-services-things-search.html) | Track changes to `Things`, `Features`, and `Policies`; maintain an optimized search index; execute search queries | +| [Gateway](architecture-services-gateway.html) | Provide [HTTP](httpapi-overview.html) and [WebSocket](httpapi-protocol-bindings-websocket.html) APIs | +| [Connectivity](architecture-services-connectivity.html) | Persist [Connections](basic-connections.html); send and receive [Ditto Protocol](protocol-overview.html) messages to/from external message brokers | -* [Policies](architecture-services-policies.html): persistence + enforcement (authorization) of [Policies](basic-policy.html) -* [Things](architecture-services-things.html): persistence + enforcement (authorization) of [Things](basic-thing.html) - and [Features](basic-feature.html) -* [Things-Search](architecture-services-things-search.html): tracking changes to `Things`, `Features`, `Policies` and - updating an optimized search index + executes queries on this search index -* [Gateway](architecture-services-gateway.html): provides HTTP and WebSocket API -* [Connectivity](architecture-services-connectivity.html): - * persistence of [Connections](basic-connections.html) - * sends [Ditto Protocol](protocol-overview.html) messages to external message brokers and receives messages from them +All services run in the same [Pekko cluster](https://pekko.apache.org/docs/pekko/current/typed/cluster-concepts.html) and reach each other via TCP without requiring an additional message broker between them. -All services run in the same [Pekko cluster](https://pekko.apache.org/docs/pekko/current/typed/cluster-concepts.html) and can -reach each other via TCP without the need for an additional message broker in between. +### Microservice definition -## Components +Each Ditto microservice follows three rules: -Ditto consists of multiple "microservices" as shown in the above component view. +1. **Own data store**: Only the owning microservice can access and write to its data store. +2. **Signal-based API**: The service exposes its API as [signals](basic-signals.html) (commands, command responses, events). +3. **Signal-only access**: Other services interact with it exclusively through these signals. -A "microservice" in Ditto is defined as: +### Communication -* has its own data store which only this microservice may access and write to -* has an API in form of [signals](basic-signals.html) (commands, command responses, events) -* can be accessed by other services only via the defined [signals](basic-signals.html) +All microservices communicate asynchronously within the Ditto cluster using [Pekko remoting](https://pekko.apache.org/docs/pekko/current/general/remoting.html). Each service acts as both a TCP server (accepting connections) and a TCP client (sending messages to other services). -## Communication +All messages sent between services are serializable. Ditto [signals](basic-signals.html) serialize from Java objects to JSON and deserialize back from JSON to Java objects. -All microservices can communicate asynchronously in a Ditto cluster. Communication is done via -[Pekko remoting](https://pekko.apache.org/docs/pekko/current/general/remoting.html) which means that each service acts as server, -providing a TCP endpoint, as well as client sending data to other services. +## Further reading -All messages which are sent between Ditto microservices must in a way be serializable and deserializable. -All Ditto [signals](basic-signals.html) can be serialized from Java objects to JSON representation and deserialized back -from JSON to Java objects. +* [Policies Service](architecture-services-policies.html) +* [Things Service](architecture-services-things.html) +* [Things-Search Service](architecture-services-things-search.html) +* [Gateway Service](architecture-services-gateway.html) +* [Connectivity Service](architecture-services-connectivity.html) diff --git a/documentation/src/main/resources/pages/ditto/architecture-services-connectivity.md b/documentation/src/main/resources/pages/ditto/architecture-services-connectivity.md index dc47211c60e..a716b5322ed 100644 --- a/documentation/src/main/resources/pages/ditto/architecture-services-connectivity.md +++ b/documentation/src/main/resources/pages/ditto/architecture-services-connectivity.md @@ -5,55 +5,58 @@ tags: [architecture, connectivity] permalink: architecture-services-connectivity.html --- -The "connectivity" service enables Ditto to establish and manage client-side connections to external service endpoints. -You can communicate with your connected things/twins over those connections via [Ditto Protocol] messages. The -connectivity service supports various transport protocols, which are bound to the [Ditto Protocol] via specific -[Protocol Bindings]. - -If you don't have the option to transform your payload to a [Ditto Protocol Message] on the client-side, the -connectivity service offers a flexible and customizable [payload mapping] on top. +The Connectivity service establishes and manages client-side connections to external message brokers, translating between [Ditto Protocol] messages and external transport protocols. -## Model +{% include callout.html content="**TL;DR**: The Connectivity service persists connection configurations, manages connections to external endpoints (AMQP, MQTT, Kafka, HTTP), translates messages to/from Ditto Protocol, and supports custom payload mapping." type="primary" %} -The model of the connectivity service is defined around the entity `Connection`: +## Overview +You use the Connectivity service to integrate Ditto with external systems. It creates client-side connections to message brokers and other endpoints, sends and receives [Ditto Protocol] messages over those connections, and maps custom payloads when needed. -* [model](https://github.com/eclipse-ditto/ditto/tree/master/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model) +If you cannot transform your payload to a [Ditto Protocol Message] on the client side, the Connectivity service provides flexible and customizable [payload mapping]. -## Signals +## How it works -Other services can communicate with the connectivity service via: +### Model -* [ConnectivityCommands](https://github.com/eclipse-ditto/ditto/tree/master/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model/signals/commands/ConnectivityCommand.java): - implementing classes provide commands which are processed by this service -* [ConnectivityEvents](https://github.com/eclipse-ditto/ditto/tree/master/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model/signals/events/ConnectivityEvent.java): - implementing classes represent events which are emitted when entities managed by this service were modified +The service is built around the `Connection` entity: -## Persistence +* [Connection model](https://github.com/eclipse-ditto/ditto/tree/master/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model) -The connectivity service uses [Pekko persistence](https://pekko.apache.org/docs/pekko/current/persistence.html?language=java) and -with that [Event sourcing](basic-signals.html#architectural-style) in order to persist changes to -and restore persisted [connections](basic-connections.html). +### Signals -## Enforcement +Other services communicate with the Connectivity service through two signal types: -The connectivity service does not enforce/authorize [connection signals](#signals) by a [policy](basic-policy.html) as -the connection does not contain a `policyId`. +* [ConnectivityCommands](https://github.com/eclipse-ditto/ditto/tree/master/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model/signals/commands/ConnectivityCommand.java): Commands for managing connections (create, modify, open, close, delete) +* [ConnectivityEvents](https://github.com/eclipse-ditto/ditto/tree/master/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model/signals/events/ConnectivityEvent.java): Events emitted when connection entities change -## Tasks +### Persistence -* create/remove connections (by persisting them) -* connect/disconnect to endpoints -* restore existing connections upon restart/failover -* translate incoming [Ditto Protocol] messages to [commands](basic-signals-command.html) - and translate [command responses](basic-signals-commandresponse.html) back to [Ditto Protocol] response messages -* map custom message protocols to the [Ditto Protocol] +The Connectivity service uses [Pekko persistence](https://pekko.apache.org/docs/pekko/current/persistence.html?language=java) with [event sourcing](basic-signals.html#overview) to persist and restore [connections](basic-connections.html). +### Enforcement +The Connectivity service does not enforce authorization through [policies](basic-policy.html) because connections do not reference a `policyId`. Access control for connection management is handled through the [DevOps user](operating-devops.html) or via the HTTP API with appropriate permissions. + +### Tasks + +The Connectivity service performs these core tasks: + +* Create and remove connections by persisting their configuration +* Connect to and disconnect from external endpoints +* Restore existing connections on restart or failover +* Translate incoming [Ditto Protocol] messages to [commands](basic-signals-command.html) and translate [command responses](basic-signals-commandresponse.html) back to [Ditto Protocol] response messages +* Map custom message protocols to the [Ditto Protocol] via [Protocol Bindings] + +## Further reading + +* [Connections concept](basic-connections.html) +* [Protocol Bindings](protocol-bindings.html) +* [Ditto Protocol Overview](protocol-overview.html) +* [Architecture Overview](architecture-overview.html) - [Ditto Protocol]: protocol-overview.html [Ditto Protocol Message]: protocol-specification-things-messages.html -[payload mapping]: protocol-specification-things-messages.html +[payload mapping]: connectivity-mapping.html [Protocol Bindings]: protocol-bindings.html diff --git a/documentation/src/main/resources/pages/ditto/architecture-services-gateway.md b/documentation/src/main/resources/pages/ditto/architecture-services-gateway.md index fc444ea7644..00d3245b6ea 100644 --- a/documentation/src/main/resources/pages/ditto/architecture-services-gateway.md +++ b/documentation/src/main/resources/pages/ditto/architecture-services-gateway.md @@ -5,28 +5,37 @@ tags: [architecture] permalink: architecture-services-gateway.html --- -The "gateway" service is responsible for providing Ditto's [HTTP](httpapi-overview.html) + -[WebSocket](httpapi-protocol-bindings-websocket.html) API. +The Gateway service provides Ditto's external-facing [HTTP](httpapi-overview.html) and [WebSocket](httpapi-protocol-bindings-websocket.html) APIs, translating between HTTP/WebSocket requests and internal Ditto signals. -## Model +{% include callout.html content="**TL;DR**: The Gateway service is the entry point for all HTTP and WebSocket traffic. It translates HTTP requests into internal commands, routes Ditto Protocol messages from WebSocket and Cloud Events, and streams change notifications back to connected clients." type="primary" %} -The gateway service has no model by its own, but uses the model of all the services it provides the HTTP + WebSocket API for. +## Overview -## Signals +The Gateway service acts as Ditto's API layer. It does not own any entities or persistence -- instead, it translates external requests into internal [signals](basic-signals.html) and forwards them to the appropriate services within the cluster. -The gateway service has no signals by its own, but uses the signals of all the services it provides the HTTP + WebSocket API for. +## How it works -## Persistence +### Model and signals -The gateway service has no persistence by its own. +The Gateway service does not define its own entity model or signal types. It uses the models and signals from all other Ditto services to provide a unified API surface. -## Tasks +### Persistence -* translate HTTP request to [commands](basic-signals-command.html) and translates [command responses](basic-signals-commandresponse.html) - back to HTTP responses -* translate [Ditto Protocol](protocol-overview.html) messages incoming via the [WebSocket](httpapi-protocol-bindings-websocket.html) - to [commands](basic-signals-command.html) and translates [command responses](basic-signals-commandresponse.html) back - to [Ditto Protocol](protocol-overview.html) response messages -* accepts [Ditto Protocol](protocol-overview.html) messages incoming via the [Cloud Events HTTP Binding](httpapi-protocol-bindings-cloudevents.html) -* subscribe for [events](basic-signals-event.html) in Ditto cluster and emits [change notifications](basic-changenotifications.html) - via connected [WebSocket](httpapi-protocol-bindings-websocket.html) clients or via [SSEs](httpapi-sse.html) +The Gateway service does not maintain any persistence of its own. + +### Tasks + +The Gateway service performs these core tasks: + +* **HTTP API**: Translate incoming HTTP requests to [commands](basic-signals-command.html) and translate [command responses](basic-signals-commandresponse.html) back to HTTP responses +* **WebSocket API**: Translate [Ditto Protocol](protocol-overview.html) messages arriving via [WebSocket](httpapi-protocol-bindings-websocket.html) to commands, and translate responses back to Ditto Protocol messages +* **Cloud Events**: Accept [Ditto Protocol](protocol-overview.html) messages via the [Cloud Events HTTP Binding](httpapi-protocol-bindings-cloudevents.html) +* **Change notifications**: Subscribe to [events](basic-signals-event.html) in the Ditto cluster and stream [change notifications](basic-changenotifications.html) to connected [WebSocket](httpapi-protocol-bindings-websocket.html) clients and [SSE](httpapi-sse.html) consumers + +## Further reading + +* [HTTP API Overview](httpapi-overview.html) +* [WebSocket Protocol Binding](httpapi-protocol-bindings-websocket.html) +* [Cloud Events Binding](httpapi-protocol-bindings-cloudevents.html) +* [SSE (Server-Sent Events)](httpapi-sse.html) +* [Architecture Overview](architecture-overview.html) diff --git a/documentation/src/main/resources/pages/ditto/architecture-services-policies.md b/documentation/src/main/resources/pages/ditto/architecture-services-policies.md index ea231875a62..4b208e93ce9 100644 --- a/documentation/src/main/resources/pages/ditto/architecture-services-policies.md +++ b/documentation/src/main/resources/pages/ditto/architecture-services-policies.md @@ -5,30 +5,39 @@ tags: [architecture] permalink: architecture-services-policies.html --- -The "policies" service takes care of persisting [Policies](basic-policy.html). +The Policies service persists and enforces [Policies](basic-policy.html), which control access to all resources in Ditto. -## Model +{% include callout.html content="**TL;DR**: The Policies service owns all Policy entities, persists them via event sourcing in MongoDB, and enforces authorization on policy-related commands using the policy itself." type="primary" %} -The model of the policies service is defined around the entity `Policy`: +## Overview +The Policies service is responsible for the complete lifecycle of [Policy](basic-policy.html) entities -- creation, modification, retrieval, and deletion. Every authorization decision for policy-level operations flows through this service. + +## How it works + +### Model + +The service is built around the `Policy` entity: * [Policy model](https://github.com/eclipse-ditto/ditto/tree/master/policies/model/src/main/java/org/eclipse/ditto/policies/model) -## Signals +### Signals + +Other services communicate with the Policies service through two signal types: + +* [PolicyCommands](https://github.com/eclipse-ditto/ditto/tree/master/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/PolicyCommand.java): Commands that the service processes (create, modify, retrieve, delete) +* [PolicyEvents](https://github.com/eclipse-ditto/ditto/tree/master/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/events/PolicyEvent.java): Events emitted when policy entities change -Other services can communicate with the policies service via: +### Persistence -* [PolicyCommands](https://github.com/eclipse-ditto/ditto/tree/master/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/PolicyCommand.java): - implementing classes provide commands which are processed by this service -* [PolicyEvents](https://github.com/eclipse-ditto/ditto/tree/master/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/events/PolicyEvent.java): - implementing classes represent events which are emitted when entities managed by this service were modified +The Policies service uses [Pekko persistence](https://pekko.apache.org/docs/pekko/current/persistence.html?language=java) with [event sourcing](basic-signals.html#overview) to persist changes and restore [policies](basic-policy.html). -## Persistence +### Enforcement -The policies service uses [Pekko persistence](https://pekko.apache.org/docs/pekko/current/persistence.html?language=java) and -with that [Event sourcing](basic-signals.html#architectural-style) in order to persist changes to -and restore persisted [policies](basic-policy.html). +The service authorizes all [policy signals](#signals) using the policy's own rules. In other words, you need the right permissions in a policy to modify that policy. -## Enforcement +## Further reading -The policies service enforces/authorizes [policy signals](#signals) by the "own" [policy](basic-policy.html). +* [Policy concept](basic-policy.html) +* [Architecture Overview](architecture-overview.html) +* [Things Service](architecture-services-things.html) diff --git a/documentation/src/main/resources/pages/ditto/architecture-services-things-search.md b/documentation/src/main/resources/pages/ditto/architecture-services-things-search.md index 05e64b1f221..55d30cad1f1 100644 --- a/documentation/src/main/resources/pages/ditto/architecture-services-things-search.md +++ b/documentation/src/main/resources/pages/ditto/architecture-services-things-search.md @@ -5,40 +5,45 @@ tags: [architecture, search] permalink: architecture-services-things-search.html --- -The "things-search" service takes care of: +The Things-Search service maintains an optimized search index of all Things and executes search queries against it. -* updating an optimized search index of `Things` based on the [events](basic-signals-event.html) emitted by the - [things](architecture-services-things.html) and [policies](architecture-services-policies.html) services when entities - are changed there -* executing search queries against the search index in order to find out which `Things` (which `thingId`s) match a - given search +{% include callout.html content="**TL;DR**: The Things-Search service listens for Thing and Policy change events, updates a search-optimized MongoDB collection, and processes RQL search queries to find matching Things." type="primary" %} -## Model +## Overview -The things-search service has no model (entity) by its own, but uses the model of [things](architecture-services-things.html) -and [policies](architecture-services-policies.html) services. +The Things-Search service has two main responsibilities: -It however contains a model which can transform an RQL -search query into a Java domain model which is defined here: +1. **Index maintenance**: Track changes to Things, Features, and Policies by consuming [events](basic-signals-event.html) from the [Things](architecture-services-things.html) and [Policies](architecture-services-policies.html) services, and keep a search-optimized index up to date. +2. **Query execution**: Accept search queries and return matching Thing IDs from the index. -* [rql parser ast](https://github.com/eclipse-ditto/ditto/tree/master/rql/model/src/main/java/org/eclipse/ditto/rql/model/predicates/ast) +## How it works -## Signals +### Model -Other services can communicate with the things-search service via: +The Things-Search service does not define its own entity model. It uses the models from the [Things](architecture-services-things.html) and [Policies](architecture-services-policies.html) services. +It does include a parser that transforms [RQL](basic-rql.html) search queries into a Java domain model: -* [commands](https://github.com/eclipse-ditto/ditto/tree/master/thingsearch/model/src/main/java/org/eclipse/ditto/thingsearch/model/signals/commands): - containing commands and command responses which are processed by this service +* [RQL parser AST](https://github.com/eclipse-ditto/ditto/tree/master/rql/model/src/main/java/org/eclipse/ditto/rql/model/predicates/ast) -## Persistence +### Signals -The Things-Search service maintains its own persistence in which it stores `Things` in an optimized way in order to -provide a full search on arbitrary `Thing` data. +Other services communicate with the Things-Search service via: -Things-Search creates the following MongoDB collections: +* [Commands](https://github.com/eclipse-ditto/ditto/tree/master/thingsearch/model/src/main/java/org/eclipse/ditto/thingsearch/model/signals/commands): Search commands and their responses -* `search`: The search index. -* `searchSync`: A single-document capped collection containing the instant until which `Thing` events are -indexed for sure. +### Persistence +The Things-Search service maintains its own MongoDB collections optimized for full-text and attribute-based search: + +| Collection | Purpose | +|------------|---------| +| `search` | The search index containing Thing data in a search-optimized format | +| `searchSync` | A single-document capped collection that records the instant until which Thing events are indexed | + +## Further reading + +* [Search concept](basic-search.html) +* [Architecture Overview](architecture-overview.html) +* [Things Service](architecture-services-things.html) +* [Policies Service](architecture-services-policies.html) diff --git a/documentation/src/main/resources/pages/ditto/architecture-services-things.md b/documentation/src/main/resources/pages/ditto/architecture-services-things.md index 8687e892258..7d40d43383b 100644 --- a/documentation/src/main/resources/pages/ditto/architecture-services-things.md +++ b/documentation/src/main/resources/pages/ditto/architecture-services-things.md @@ -5,30 +5,40 @@ tags: [architecture] permalink: architecture-services-things.html --- -The "things" service takes care of persisting [Things](basic-thing.html) and [Features](basic-feature.html). +The Things service persists and enforces authorization for [Things](basic-thing.html) and [Features](basic-feature.html), which represent your digital twins. -## Model +{% include callout.html content="**TL;DR**: The Things service owns all Thing and Feature entities, persists them via event sourcing in MongoDB, and enforces authorization using the Policy referenced by each Thing's `policyId`." type="primary" %} -The model of the things service is defined around the entities `Thing` and `Feature`: +## Overview + +The Things service manages the full lifecycle of [Thing](basic-thing.html) and [Feature](basic-feature.html) entities. It handles creation, modification, retrieval, and deletion, and it enforces access control on every operation. + +## How it works + +### Model + +The service is built around two entities, `Thing` and `Feature`: * [Thing model](https://github.com/eclipse-ditto/ditto/tree/master/things/model/src/main/java/org/eclipse/ditto/things/model) -## Signals +### Signals + +Other services communicate with the Things service through two signal types: + +* [ThingCommands](https://github.com/eclipse-ditto/ditto/tree/master/things/model/src/main/java/org/eclipse/ditto/things/model/signals/commands/ThingCommand.java): Commands that the service processes (create, modify, retrieve, delete) +* [ThingEvents](https://github.com/eclipse-ditto/ditto/tree/master/things/model/src/main/java/org/eclipse/ditto/things/model/signals/events/ThingEvent.java): Events emitted when Thing or Feature entities change -Other services can communicate with the things service via: +### Persistence -* [ThingCommands](https://github.com/eclipse-ditto/ditto/tree/master/things/model/src/main/java/org/eclipse/ditto/things/model/signals/commands/ThingCommand.java): - implementing classes provide commands which are processed by this service -* [ThingEvents](https://github.com/eclipse-ditto/ditto/tree/master/things/model/src/main/java/org/eclipse/ditto/things/model/signals/events/ThingEvent.java): - implementing classes represent events which are emitted when entities managed by this service were modified +The Things service uses [Pekko persistence](https://pekko.apache.org/docs/pekko/current/persistence.html?language=java) with [event sourcing](basic-signals.html#overview) to persist changes and restore [things](basic-thing.html). -## Persistence +### Enforcement -The things service uses [Pekko persistence](https://pekko.apache.org/docs/pekko/current/persistence.html?language=java) and -with that [Event sourcing](basic-signals.html#architectural-style) in order to persist changes to -and restore persisted [things](basic-thing.html). +The service authorizes all [thing signals](#signals) using the [Policy](basic-policy.html) referenced by the Thing's `policyId`. You must have the appropriate permissions granted in the referenced policy to perform operations on a Thing. -## Enforcement +## Further reading -The things service enforces/authorizes [thing signals](#signals) by the via `policyId` referenced -[policy](basic-policy.html). +* [Thing concept](basic-thing.html) +* [Feature concept](basic-feature.html) +* [Architecture Overview](architecture-overview.html) +* [Policies Service](architecture-services-policies.html) diff --git a/documentation/src/main/resources/pages/ditto/basic-acknowledgements.md b/documentation/src/main/resources/pages/ditto/basic-acknowledgements.md index f84fff39dc4..326f285ef35 100644 --- a/documentation/src/main/resources/pages/ditto/basic-acknowledgements.md +++ b/documentation/src/main/resources/pages/ditto/basic-acknowledgements.md @@ -5,101 +5,78 @@ tags: [model] permalink: basic-acknowledgements.html --- -Acknowledgements are a concept in Ditto used to indicate that a -[supported signal](#supported-signal-types) was successfully received or processed by either an -internal Ditto functionality or an external subscriber of that signal. +Acknowledgements let you confirm that a signal was successfully received or processed, either by Ditto internally or by an external subscriber. -Acknowledgements can be seen as (potentially multiple) responses to a single signal like for example a twin command. -This means that Ditto collects all the [requested acknowledgements](#requesting-acks) until the signal is -successfully processed within a specified timeout interval. +{% include callout.html content="**TL;DR**: Request acknowledgements (via the `requested-acks` header) to get confirmation that a command was persisted, forwarded, or processed by subscribers. Use them to achieve 'at least once' delivery guarantees (QoS 1)." type="primary" %} -## Usage scenario examples -Acknowledgements are useful for accomplishing the following example tasks: +## Overview -* Postpone a response to an API request (e.g. block an HTTP request) until one or more specific actions were performed - in Ditto (e.g. a modification was successfully persisted). -* Postpone a response until an external subscriber connected to Ditto reports that it successfully processed an - [event](basic-signals-event.html) which e.g. resulted by a persistence change of Ditto. -* Provide a QoS (quality of service) guarantee of "at least once" when processing - messages in an end-to-end manner by e.g. technically acknowledging/settling a processed signal from a message broker - (e.g. [AMQP 1.0](connectivity-protocol-bindings-amqp10.html) or [MQTT](connectivity-protocol-bindings-mqtt.html)) only - after it was successfully applied to Ditto and potentially also to 3rd parties. +Acknowledgements act as (potentially multiple) responses to a single signal such as a twin command. Ditto collects all [requested acknowledgements](#requesting-acks) until the signal is successfully processed or a timeout expires. +## Usage scenarios + +Acknowledgements help you accomplish tasks like: + +* **Block until persisted**: Postpone an API response until a modification is persisted in Ditto. +* **Block until consumed**: Postpone a response until an external subscriber confirms it processed the resulting event. +* **At-least-once delivery**: Technically acknowledge a message from a broker (AMQP, MQTT) only after Ditto has processed it and optionally forwarded it to third parties. ## Supported signal types -For the following signal types it is possible to define that certain acknowledgements are *requested*. -* [Commands](basic-signals-command.html) *modifying the state of a twin* (Twin commands), -* Live commands, -* Live messages. +You can request acknowledgements for: + +* [Commands](basic-signals-command.html) that modify twin state (twin commands) +* Live commands +* Live messages ## Acknowledgement labels -A common (matching) label links acknowledgement requests and their corresponding acknowledgements. -Ditto already uses some labels for its built-in acknowledgement requests. -Those labels may not be used when sending back a custom acknowledgement. + +A label links an acknowledgement request to its corresponding acknowledgement. Ditto uses some labels internally; you cannot use those labels for custom acknowledgements. ### Built-in acknowledgement labels -Ditto provides built-in acknowledgement requests that are automatically fulfilled on certain actions within the Ditto -cluster: -* **twin-persisted**: For acknowledgement requests of twin modifying commands. -It is fulfilled when a modifying command has successfully updated the digital twin in Ditto's persistence. -It is ignored for commands in the live channel. -* **live-response**: For acknowledgement requests of live commands and live messages. -It is fulfilled when a subscriber of the live command or message sends a corresponding response. -It is ignored for commands in the twin channel. -* **search-persisted**: For acknowledgement requests of twin modifying commands. -It is fulfilled when a modifying command has successfully updated the search index of the digital twin. -It is ignored for commands in the live channel. + +Ditto automatically fulfills these labels: + +* **twin-persisted**: Fulfilled when a modifying command successfully updates the digital twin in Ditto's persistence. Ignored for live channel commands. +* **live-response**: Fulfilled when a subscriber of a live command or message sends a corresponding response. Ignored for twin channel commands. +* **search-persisted**: Fulfilled when a modifying command successfully updates the search index. Ignored for live channel commands. ### Custom acknowledgement labels -In addition to the [built-in](#built-in-acknowledgement-labels) acknowledgement requests, -a supported signal can contain custom acknowledgement requests. -A subscriber of such a signal can detect a requested acknowledgement via the `"requested-acks"` header. -If the subscriber is in charge of handling a requested acknowledgement it -[issues an acknowledgement](#issuing-acknowledgements). +In addition to built-in labels, you can include custom acknowledgement requests in supported signals. A subscriber detects requested labels via the `"requested-acks"` header and [issues an acknowledgement](#issuing-acknowledgements) for the labels it handles. + +## Acknowledgement structure -## Acknowledgements (ACKs) -A single acknowledgement contains the following information: -* Acknowledgement label (one of the requested labels of the [ack requests](#requesting-acks)) -* Header fields - * mandatory: **correlation-id** the same correlation ID as the one of the signal which requested the acknowledgement - * optional: additional header fields -* Status code (HTTP status code semantic) defining whether an acknowledgement was successful or not -* Optional payload as JSON +A single acknowledgement contains: -The [Ditto Protocol specification](protocol-specification-acks.html) describes in detail what is contained.
    -An example of how acknowledgements in Ditto Protocol look like can be found at the -[acknowledgement examples](protocol-examples.html#acknowledgements-acks) section. +* **Label**: one of the requested labels +* **Headers**: must include `correlation-id` matching the original signal; may include additional headers +* **Status code**: HTTP-semantics status code indicating success or failure +* **Payload** (optional): JSON data +The [Ditto Protocol specification](protocol-specification-acks.html) describes the format in detail. See the [acknowledgement examples](protocol-examples.html#acknowledgements-acks) for sample messages. -## Requesting ACKs -With every supported signal there is the option to request acknowledgements. -Acknowledgement requests are expressed as protocol specific header fields of signals. -The following sections explain the various ways of requesting acknowledgements. +## Requesting ACKs -[Events](basic-signals-event.html) emitted by Ditto will include the custom acknowledgement requests in the -`"requested-acks"` header. +You request acknowledgements via headers on supported signals. + +[Events](basic-signals-event.html) emitted by Ditto include the custom acknowledgement requests in the `"requested-acks"` header. ### Requesting ACKs via HTTP -Either specify the following HTTP header fields: - * **requested-acks**: a comma separated list of [acknowledgement labels](#acknowledgement-labels).
    - Example: `requested-acks: twin-persisted,some-connection-id:my-custom-ack`. - * **timeout**: an optional time interval (in ms, s or m) to define how long the HTTP request should wait for - acknowledgements and block. - Default and maximum value: `60s`.
    - Examples: `timeout: 42s`, `timeout: 250ms`, `timeout: 1m`. - -Or specify the header fields as query parameters to the HTTP params, e.g.: + +Set these HTTP headers: + +* **requested-acks**: comma-separated list of [acknowledgement labels](#acknowledgement-labels). Example: `requested-acks: twin-persisted,some-connection-id:my-custom-ack` +* **timeout**: how long to block (default and max: `60s`). Examples: `timeout: 42s`, `timeout: 250ms`, `timeout: 1m` + +Or use query parameters: + ``` PUT /api/2/things/org.eclipse.ditto:thing-1?requested-acks=twin-persisted,my-custom-ack&timeout=42s ``` -The response of an HTTP request, which requested several acknowledgements, will differ from the response to an HTTP -request without acknowledgement requests. +**Example response** -- all acknowledgements successful (overall status `200`): -Example response when 2 acknowledgements were requested and were successful. The overall HTTP status code will be -`200` (OK) in this case: ```json { "twin-persisted": { @@ -117,9 +94,7 @@ Example response when 2 acknowledgements were requested and were successful. The }, "my-custom-ack": { "status": 200, - "payload": { - "outcome": "green" - }, + "payload": { "outcome": "green" }, "headers": { "version": 2, "correlation-id": "db878735-4957-4fd9-92dc-6f09bb12a093" @@ -128,8 +103,8 @@ Example response when 2 acknowledgements were requested and were successful. The } ``` -Example response when 2 acknowledgements were requested and one lead to a timeout. The overall HTTP status code will be -`424` (Dependency failed) in this case: +**Example response** -- one acknowledgement timed out (overall status `424`): + ```json { "twin-persisted": { @@ -153,293 +128,199 @@ Example response when 2 acknowledgements were requested and one lead to a timeou "message": "The acknowledgement request reached the specified timeout of 42,000ms.", "description": "Try increasing the timeout and make sure that the requested acknowledgement is sent back in time." }, - "headers": { - "version": 2, - "correlation-id": "db878735-4957-4fd9-92dc-6f09bb12a093" - } + "headers": { "correlation-id": "db878735-4957-4fd9-92dc-6f09bb12a093" } } } ``` ### Requesting ACKs via WebSocket -Together with a received Ditto [command](basic-signals-command.html) in [Ditto Protocol](protocol-specification.html), -`"requested-acks"` (as JsonArray of strings) and `"timeout"` headers in the -[Ditto Protocol headers](protocol-specification.html#headers) can be included in order to request acknowledgements via -WebSocket. -The response will be an (aggregating) [acknowledgements](protocol-specification-acks.html#acknowledgements-aggregating) -message. +Include `"requested-acks"` (JSON array of strings) and `"timeout"` in the [Ditto Protocol headers](protocol-specification.html#headers). The response is an [aggregated acknowledgements](protocol-specification-acks.html#acknowledgements-aggregated) message. ### Requesting ACKs via connections -Acknowledgements for Ditto managed [connection sources](basic-connections.html#sources) can be requested in 2 ways: -* specifically for each consumed supported signal as part of - the [Ditto Protocol headers](protocol-specification.html#headers) `"requested-acks"` (as JsonArray of strings) -* by configuring the managed connection source to - [request acknowledgements for all consumed supported signals](basic-connections.html#source-acknowledgement-requests). - -#### Requesting ACKs via Ditto Protocol message -Together with a received Ditto [command](basic-signals-command.html) in [Ditto Protocol](protocol-specification.html), -`"requested-acks"` (as JsonArray of strings) and `"timeout"` headers in the -[Ditto Protocol headers](protocol-specification.html#headers) can be included in order to request acknowledgements -via established connections consuming messages from [sources](basic-connections.html#sources). - -The response will be an (aggregating) [acknowledgements](protocol-specification-acks.html#acknowledgements-aggregating) -message. - -#### Requesting ACKs via connection source configuration -[Connection sources](basic-connections.html#sources) can be -[configured to add specific acknowledgement requests](basic-connections.html#source-acknowledgement-requests) for each -consumed message of the underlying physical connection (e.g. to a message broker). - -This can be used in order to ensure that e.g. all messages consumed from a single source should be processed in an -"at least once" mode (QoS 1). -E.g. if configured that the [built-in](#built-in-acknowledgement-labels) `twin-persisted` acknowledgement is requested, -a received twin-modifying command will only be technically acknowledged to the connection channel if Ditto successfully -applied and persisted the command. - -{% include note.html content="These requested acknowledgements will be appended after payload mapping is applied.
    + +You can request acknowledgements in two ways: + +1. **Per signal**: Set `"requested-acks"` in the [Ditto Protocol headers](protocol-specification.html#headers) of each consumed signal. +2. **Per source**: [Configure the connection source](basic-connections.html#source-acknowledgement-requests) to add acknowledgement requests to all consumed signals. + +{% include note.html content="These requested acknowledgements will be appended after payload mapping is applied.
    This means, that in case you decided to split your message into multiple messages, all of these messages will request the same acknowledgements.
    If this is not what you want to achieve, have a look at [how to add acknowledgement requests during payload mapping](#requesting-acks-via-ditto-protocol-message-in-payload-mapping)." %} #### Requesting ACKs via Ditto Protocol message in payload mapping -During inbound payload mapping, you can create one or more Ditto Protocol messages. - -If you configured your connection source to add requested acknowledgements to your commands, this will cause all -produced messages to request the same acknowledgements.
    -If you however want to add requested acknowledgements only to some of those created messages, you need to set the -`"requested-acks"` header (as described in -[Requesting ACKs via Ditto Protocol message](#requesting-acks-via-ditto-protocol-message) section) during payload -mapping for those commands you like to request an acknowledgement. +During inbound payload mapping, you can set `"requested-acks"` headers on individual messages rather than applying the source-level configuration uniformly. ## Issuing acknowledgements -Acknowledgements are issued by subscribers of events generated by twin-modifying commands, or by subscribers of -live commands and live messages. In order to issue a single acknowledgement, a -[Ditto Protocol acknowledgement](protocol-specification-acks.html#acknowledgement) message has to be built and sent -back, using the same `"correlation-id"` in the [protocol headers](protocol-specification.html#headers) as contained -in the received twin event, live command or live message. -The labels of issued acknowledgements are globally unique for each subscriber. Before a subscriber is allowed to -send acknowledgements, it must declare the labels of acknowledgements it sends. Any declared label taken by another -subscriber causes an appropriate error for each channel that may issue acknowledgements. +Subscribers of twin events, live commands, or live messages issue acknowledgements by sending a [Ditto Protocol acknowledgement](protocol-specification-acks.html#acknowledgement) message with the same `"correlation-id"` as the received signal. -### Semantics of status codes for issued acknowledgements +Each subscriber must declare the labels of acknowledgements it sends. Any label already declared by another subscriber causes an error. -The `status` code of issued acknowledgements have certain semantics when it comes to whether Ditto shall e.g. try to -retry processing a consumed message (when it was consumed from a message broker which persisted the message). +### Status code semantics -Application specific code invoking a Ditto API and requesting acknowledgements should also adapt the semantics in order -to correctly perform retries: +| Status code | Description | Should retry | Reasoning | +|---|---|---|---| +| `2xx` | Successfully processed | No | Received and processed. | +| `4xx` | Request error | No | Bad request or auth failure -- retrying yields the same result. | +| `408` | Request timeout | Yes | Timeout -- retrying may succeed. | +| `424` | Mixed status codes | Yes | Contains a timeout or server error. | +| `5xx` | Server error | Yes | Temporary backend error. | -| Status code | Description | Should be retried | Reasoning | -| --- | --- | --- | --- | -| `2xx` | Successfully processed / ACKed | ❌ | Successfully received + processed. | -| `4xx` | Request was erroneous | ❌ | Request could not be understood or authentication failed,
    so retrying would result in the same outcome. | -| `408` | Request timeout | ✔ | HTTP semantic for request timeout,
    retrying does make sense. | -| `424` | Mixed status codes,
    with at least one request error | ✔ | There could be a timeout or server error in the mixed status codes,
    so retrying does make sense. | -| `5xx` | Server error | ✔ | The processing backend encountered a temporary error,
    retrying could solve the issue. | +### Issuing ACKs via HTTP -### Issuing ACKs via Ditto's HTTP API -It is not possible to issue acknowledgements via Ditto's HTTP API, because it is impossible to subscribe for twin events, -live commands or live messages via HTTP. +You cannot issue acknowledgements via Ditto's HTTP API. HTTP does not support subscribing to +twin events, live commands, or live messages -- use WebSocket or managed connections instead. ### Issuing ACKs via WebSocket -Create and send the [Ditto Protocol acknowledgement](protocol-specification-acks.html#acknowledgement) message over an -established WebSocket in response to a twin event, live command or live message that contains a `"requested-acks"` -header. -Only acknowledgements with declared labels are accepted. To declare acknowledgement labels, set them as the value -of the query parameter `declared-acks` as comma-separated list: +Send the [acknowledgement message](protocol-specification-acks.html#acknowledgement) over the WebSocket. Declare labels via the `declared-acks` query parameter: + ``` GET /ws/2?declared-acks=some-connection-id:ack-label-1,my:ack-label-2 ``` -The websocket will be closed right after it has been opened if any other another subscriber already declared the same label.
    -This means that it is not possible to establish a second websocket connection with the same declared acknowledgements -before closing the first one. +The WebSocket closes immediately if another subscriber already declared the same label. -{% include warning.html content="Therefore, it is not recommended relying on the websocket API for high - availability scenarios." +{% include warning.html content="Therefore, it is not recommended relying on the websocket API for high + availability scenarios." %} ### Issuing ACKs via connections -Requested acknowledgements for Ditto managed [connection targets](basic-connections.html#targets) can be issued in 2 -ways: - -* specifically for each published twin event, live command or live message by sending a - [Ditto Protocol acknowledgement](protocol-specification-acks.html#acknowledgement) back, - via a source of the same connection; -* by configuring the managed connection target to automatically - [issue acknowledgements](basic-connections.html#target-issued-acknowledgement-label) for all published twin events, - live commands and live messages that request them. - -Acknowledgements sent via a source must -[have their labels declared](basic-connections.html#source-declared-acknowledgement-labels) -in the field `declaredAcks` as a JSON array.
    -The labels of target-issued acknowledgements are declared automatically.
    -Acknowledgement labels of a connection must be prefixed by the connection ID or the `{%raw%}{{connection:id}}{%endraw%}` -placeholder followed by a colon, for example `{%raw%}{{connection:id}}:my-custom-ack{%endraw%}`. - -If some source-declared or target-issued acknowledgement labels are taken by a websocket subscriber, -all acknowledgements sent by the connection are rejected with error until the websocket is closed. - -#### Issuing ACKs via Ditto Protocol acknowledgement message -Create and send the [Ditto Protocol acknowledgement](protocol-specification-acks.html#acknowledgement) message over a -source of the connection, in response to an event, live command or live message which contained an `"requested-acks"` -header. - -#### Issuing ACKs via connection target configuration -[Connection targets](basic-connections.html#targets) can be configured to -[issue certain acknowledgements automatically](basic-connections.html#target-issued-acknowledgement-label) -for each twin event, live command or live message published to the underlying physical connection (e.g. to a message -broker). - -This can be used in order to automatically issue technical acknowledgements once an event, live command or live message -was published to an HTTP endpoint or into a message broker. When this target guarantees having processed the event -via its protocol (see the -[defined semantics of the status codes](#semantics-of-status-codes-for-issued-acknowledgements)), a successful -acknowledgement is created and returned to the requester. +You can issue acknowledgements in two ways: + +1. **Via source**: Send a [Ditto Protocol acknowledgement](protocol-specification-acks.html#acknowledgement) message through a source of the same connection. Labels must be [declared](basic-connections.html#source-declared-acknowledgement-labels) in the `declaredAcks` field as a JSON array. +2. **Via target configuration**: [Configure the target](basic-connections.html#target-issued-acknowledgement-label) to automatically issue acknowledgements for each twin event, live command, or live message published to the underlying channel (e.g., a message broker). When the target confirms delivery via its transport protocol (see [status code semantics](#status-code-semantics)), Ditto creates a successful acknowledgement and returns it to the requester. Target-issued labels are declared automatically. + +Acknowledgement labels for connections must be prefixed by the connection ID or the `{%raw%}{{connection:id}}{%endraw%}` placeholder followed by a colon, e.g., `{%raw%}{{connection:id}}:my-custom-ack{%endraw%}`. + +If a source-declared or target-issued label conflicts with a label declared by a WebSocket subscriber, all acknowledgements from the connection are rejected until the WebSocket is closed. ## Quality of Service -### QoS 0 - at most once -By default, Ditto processes all messages/commands processed in an "at most once" (or QoS 0) semantic. -For many of the use cases in the IoT, QoS 0 is sufficient, e.g. when processing telemetry data of a sensor: -if one sensor value is not applied to the digital twin there will soon follow the next sensor reading, and the twin will -be eventually up to date again. - -### QoS 1 - at least once -However, there are IoT use cases where it is of upmost importance that a signal is processed "at least once" (or QoS 1), -e.g. in order to guarantee that it was persisted in the digital twin or that an [event](basic-signals-event.html) -consumer connected to Ditto did successfully receive a [notification](basic-changenotifications.html) which resulted -from a [command](basic-signals-command.html) which Ditto received and processed. - -The "acknowledgements" concept documented on this page provides means by which supported signals Ditto consumes and -processes are treated with an "at least once" (or QoS 1) semantic. - -[Create/modify commands](protocol-specification-things-create-or-modify.html) will technically be acknowledged on the -sent channel (e.g. HTTP or WebSocket or any [connection type](basic-connections.html#connection-types)) when it was a -success.
    -If it could not be applied successfully, the signal will be negatively acknowledged. -The [status code of the acknowledgement](protocol-specification-acks.html#combined-status-code) reflects the failure. - -Based on the used channel, the acknowledgement will be translated to the capabilities of the command or live message -channel, e.g. for HTTP an HTTP response will be sent with the outcome as HTTP status (`2xx` for a successful -acknowledgement, and `4xx` for a non-successful one) together with additional details as HTTP response. - -### Assure QoS until persisted in Ditto - twin-persisted -In order to ensure that a [create/modify command](protocol-specification-things-create-or-modify.html) resulted in a -successful update of twin in Ditto's managed database, [request the acknowledgement](#requesting-acks) for -the [built-in "twin-persisted"](#built-in-acknowledgement-labels) acknowledgement label. - -### Assure QoS until processing of a live command/message by a subscriber - live-response -In order to ensure that a live command or live message is processed by a subscriber, set `response-required` to true. -This way the [built-in "live-response" acknowledgement label](#built-in-acknowledgement-labels) is [automatically requested](#requesting-acks) for the live command/message. -This acknowledgement request is fulfilled when the subscriber sends a live response or message response. - -### Assure QoS until processing of a twin event or live command/message by subscribers - custom label -In order to ensure that a [create/modify command](protocol-specification-things-create-or-modify.html) resulted in an -event which was consumed by an application integrating with Ditto, or that a live command or live message is consumed -without any live or message response, [request the acknowledgement](#requesting-acks) for a -[custom acknowledgement label](#custom-acknowledgement-labels). +### QoS 0 -- at most once + +By default, Ditto processes signals with "at most once" semantics. For many IoT use cases (e.g., telemetry data), this is sufficient -- if one sensor reading is lost, the next one updates the twin. + +### QoS 1 -- at least once + +For critical signals, use acknowledgements to achieve "at least once" delivery. Modify commands +are technically acknowledged on the transport channel (HTTP, WebSocket, or any +[connection type](basic-connections.html#supported-connection-types)) only when processing succeeds. On failure, +the signal is negatively acknowledged (e.g., AMQP NACK, MQTT no-PUBACK), triggering redelivery +by the broker. + +The [acknowledgement status code](protocol-specification-acks.html#combined-status-code) reflects the +outcome. For HTTP, this translates to `2xx` for success and `4xx` for failure. + +#### Ensure persistence -- twin-persisted + +Request the [built-in `twin-persisted`](#built-in-acknowledgement-labels) label to confirm that a +modify command successfully updated the digital twin in Ditto's database. + +#### Ensure live processing -- live-response + +Set `response-required: true` on live commands or live messages. This automatically adds +`live-response` to `requested-acks`. The acknowledgement is fulfilled when a subscriber sends a +live response or message response. + +#### Ensure subscriber consumption -- custom labels + +Request a [custom acknowledgement label](#custom-acknowledgement-labels) to confirm that an +external subscriber processed the resulting event or live command. The subscriber must +[issue the acknowledgement](#issuing-acknowledgements) via WebSocket or a connection source. ## Weak Acknowledgements (WACKs) -Since there are scenarios where the subscriber of events or live messages has defined an RQL filter or is not allowed to receive an event by a policy, it is not always possible that an acknowledgement can be provided. -To avoid that a command fails because of a missing acknowledgement for those reasons, we introduced weak acknowledgements. +When a subscriber has an RQL filter that excludes an event, or when a policy prevents delivery, +Ditto cannot obtain a real acknowledgement. To prevent commands from failing due to missing acks +in these cases, Ditto issues **weak acknowledgements** automatically. -These weak acknowledgements are issued automatically by ditto, in case a message or an event is filtered by a subscriber which declared to provide one or more of the requested acknowledgements for the command. -A weak acknowledgement can be identified by checking the header with value `ditto-weak-ack`. -Weak acknowledgements have this header set to `true`. +Weak acknowledgements are issued when a subscriber that declared one or more of the requested +acknowledgement labels filters out the event or message (e.g., via an RQL filter on a connection +target or a policy restriction). + +Identify weak acknowledgements by checking the `ditto-weak-ack` header: + +```json +{ + "my-connection:my-ack": { + "status": 200, + "headers": { + "correlation-id": "...", + "ditto-weak-ack": true + } + } +} +``` -These weak acknowledgements do not cause redelivery of messages consumed by a Connection. +Weak acknowledgements do **not** cause redelivery of messages consumed by a connection. ## Interaction between headers -Three headers control how Ditto responds to a command: `response-required`, `requested-acks`, `timeout`. -* `response-required`: `true` or `false`.
    - It governs whether the user gets a (detailed) reply.
    - In case of a live message or a live command it also has impact on the `requested-acks`: - * If `response-required` is `true`, the acknowledgement label `live-response` will be added to `requested-acks` if not - present and `requested-acks` was not explicitly set to an empty JSON array.
    - * If it is `false`, the acknowledgement label `live-response` will be removed from `requested-acks` if present. -* `requested-acks`: JSON array of acknowledgement requests.
    - It determines the content of the response and transport-layer message settlement. -* `timeout`: Duration. - It governs how long Ditto waits for responses and acknowledgements. - -It is considered a client error if `timeout` is set to `0s` while `response-required` is `true` or `requested-acks` is -nonempty. + +Three headers control how Ditto responds: `response-required`, `requested-acks`, and `timeout`. + +* **response-required**: `true` or `false`. Controls whether the caller gets a detailed reply. For live messages/commands, it also affects `requested-acks`: + * `true`: adds `live-response` to `requested-acks` if not present (unless `requested-acks` was explicitly set to empty). + * `false`: removes `live-response` from `requested-acks`. +* **requested-acks**: JSON array of acknowledgement labels. Determines response content and transport-layer settlement. +* **timeout**: Duration. How long Ditto waits for responses and acknowledgements. + +It is a client error to set `timeout` to `0s` while `response-required` is `true` or `requested-acks` is nonempty. ### Default header values -Ditto set each of the three headers `response-required`, `requested-acks`, `timeout` to a default value according to any -values of the other two headers set by the user. -The default values depend only on headers set by the user; they do not depend on each other. -Setting the default header values this way never produces any combination considered a client error unless the headers -set by the user already cause a client error. -| Header | Default value | Default value if all three headers are not set | -| --- | --- | --- | -| response-required | `false` if `timeout` is zero or `requested-acks` is empty, `true` otherwise | `true` | -| requested-acks | `empty` if `timeout` is zero or `response-required` is `false`, the channel's default acknowledgement request otherwise |`["twin-persisted"]` for TWIN channel,
    `["live-response"]` for LIVE channel | -| timeout | `60s` | `60s` | +Ditto sets defaults for each header based on the other headers: -The following sections show how each Ditto API interprets the three headers. +| Header | Default value | Default (all unset) | +|---|---|---| +| response-required | `false` if timeout is zero or requested-acks is empty; `true` otherwise | `true` | +| requested-acks | empty if timeout is zero or response-required is false; channel default otherwise | `["twin-persisted"]` (TWIN) / `["live-response"]` (LIVE) | +| timeout | `60s` | `60s` | ### HTTP -Since an HTTP response always follows an HTTP request, the header `response-required` is interpreted as whether -the user wants a *detailed* response. -If it is set to `false`, the HTTP response consists of status line and headers without body, or with a minimal body -containing other status codes. -If acknowledgements are requested, the HTTP response is delayed until all requested acknowledgements are received. -Generally, if a request cannot be answered within the defined timeout, the HTTP response has status code 408. -A response containing successful acknowledgements (2xx) and at least one failed acknowledgement (4xx) has status code -424 (failed dependency). -In this case the status codes of all acknowledgements should be check to determine the one which caused the failure. - -| API | response-required | requested-acks | timeout | Outcome | -| --- | --- | --- | --- | --- | -| HTTP | false | empty | zero | 202 Accepted immediately | -| HTTP | false | empty | non-zero | 202 Accepted immediately | -| HTTP | false | non-empty | zero | 400 Bad Request: timeout may not be zero if acknowledgements are requested | -| HTTP | false | non-empty | non-zero | 202 Accepted after receiving the requested acknowledgements | -| HTTP | true | empty | zero | 400 Bad Request: timeout may not be zero if response is required | -| HTTP | true | empty | non-zero | Response | -| HTTP | true | non-empty | zero | 400 Bad Request: timeout may not be zero if response is required | -| HTTP | true | non-empty | non-zero | Aggregated response and acknowledgements | + +| response-required | requested-acks | timeout | Outcome | +|---|---|---|---| +| false | empty | zero | 202 Accepted immediately | +| false | empty | non-zero | 202 Accepted immediately | +| false | non-empty | zero | 400 Bad Request | +| false | non-empty | non-zero | 202 Accepted after receiving acks | +| true | empty | zero | 400 Bad Request | +| true | empty | non-zero | Response | +| true | non-empty | zero | 400 Bad Request | +| true | non-empty | non-zero | Aggregated response and acks | ### WebSocket -In the absence of client errors, a response is sent for a command if and only if `response-required` is set to `true`. -Ditto supports no transport-layer message settlement for WebSocket; acknowledgements are only received as text frames. -Consequently, it is considered a client error to have non-empty `requested-acks` while `response-required` is set to -`false`. - -| API | response-required | requested-acks | timeout | Outcome | -| --- | --- | --- | --- | --- | -| WebSocket | false | empty | zero | No response | -| WebSocket | false | empty | non-zero | No response | -| WebSocket | false | non-empty | zero | Error: timeout may not be zero if acknowledgements are requested | -| WebSocket | false | non-empty | non-zero | Error: WebSocket cannot send acknowledgements without a response | -| WebSocket | true | empty | zero | Error: timeout may not be zero if response is required | -| WebSocket | true | empty | non-zero | Response | -| WebSocket | true | non-empty | zero | Error: timeout may not be zero if response is required | -| WebSocket | true | non-empty | non-zero | Aggregated response and acknowledgements | + +| response-required | requested-acks | timeout | Outcome | +|---|---|---|---| +| false | empty | zero | No response | +| false | empty | non-zero | No response | +| false | non-empty | zero | Error | +| false | non-empty | non-zero | Error: cannot send acks without response | +| true | empty | zero | Error | +| true | empty | non-zero | Response | +| true | non-empty | zero | Error | +| true | non-empty | non-zero | Aggregated response and acks | ### Connectivity -For any incoming supported signal through a connection source, the header `response-required` determines whether a -response message is published at the reply-target of the source. -The header `requested-acks` determines the transport-layer message settlement and the content of any response message -published at the reply-target. -Examples of transport-layer message settlement mechanisms are AMQP 0.9.1 consumer acknowledgement mode, AMQP 1.0 -disposition frames, and MQTT PUBACK/PUBREC/PUBREL messages for incoming PUBLISH with QoS 1 or 2. - -| API | response-required | requested-acks | timeout | Outcome | -| --- | --- | --- | --- | --- | -| Connectivity | false | empty | zero | Nothing published at reply-target;
    message settled immediately | -| Connectivity | false | empty | non-zero | Nothing published at reply-target;
    message settled immediately | -| Connectivity | false | non-empty | zero | Error published at reply-target: timeout may not be zero if acknowledgements are requested;
    message settled negatively | -| Connectivity | false | non-empty | non-zero | Nothing published at reply-target;
    message settled after receiving the requested acknowledgements | -| Connectivity | true | empty | zero | Error published at reply-target: timeout may not be zero when response is required;
    message settled negatively | -| Connectivity | true | empty | non-zero | Response published at reply-target;
    message settled immediately | -| Connectivity | true | non-empty | zero | Error published at reply-target: timeout may not be zero if response is required;
    message settled negatively | -| Connectivity | true | non-empty | non-zero | Aggregated response and acknowledgements published at reply-target;
    message settled after receiving the requested acknowledgements | + +| response-required | requested-acks | timeout | Outcome | +|---|---|---|---| +| false | empty | zero | Nothing published; settled immediately | +| false | empty | non-zero | Nothing published; settled immediately | +| false | non-empty | zero | Error; settled negatively | +| false | non-empty | non-zero | Nothing published; settled after acks | +| true | empty | zero | Error; settled negatively | +| true | empty | non-zero | Response published; settled immediately | +| true | non-empty | zero | Error; settled negatively | +| true | non-empty | non-zero | Aggregated response and acks published; settled after acks | + +## Further reading + +- [Acknowledgement protocol specification](protocol-specification-acks.html) -- Ditto Protocol format for acks +- [Protocol examples](protocol-examples.html#acknowledgements-acks) -- sample ack messages +- [Connections](basic-connections.html) -- configuring ack requests and issuance on connections diff --git a/documentation/src/main/resources/pages/ditto/basic-apis.md b/documentation/src/main/resources/pages/ditto/basic-apis.md index 9d6fbc655e9..ec4c1ab2bfb 100644 --- a/documentation/src/main/resources/pages/ditto/basic-apis.md +++ b/documentation/src/main/resources/pages/ditto/basic-apis.md @@ -5,48 +5,4 @@ tags: [model] permalink: basic-apis.html --- -Ditto provides two ways to interact with: - -* A [REST-like HTTP API](httpapi-overview.html) with a sophisticated resource layout that allows to create, read, - update and delete Things and the Thing's data. -* A JSON-based [WebSocket API](httpapi-protocol-bindings-websocket.html) implementing the - [Ditto Protocol](protocol-overview.html). - - -## HTTP API or WebSocket? - -The two ways are **almost equally powerful** and allow the same operations to work with the Thing's data, send -messages to Things and receive messages from Things. - -* The lightweight REST-like HTTP API can be used - * on less powerful devices lacking a Java runtime or supporting other (scripting) languages like JavaScript, Python, C/C++, - * and for developing Web-based user interfaces. -* The WebSocket API proves useful for - * gathering data streams from devices or massive data from another message broker, - * real-time device monitoring, - * event-driven Web applications, - * full duplex communication scenarios, etc. - - -## Comparison by feature - -| Feature | Ditto Protocol over WebSocket | REST-like HTTP API | -|---------|--------------------------------|---------------------------| -| Things management | ✓ | ✓ | -| Features management | ✓ | ✓ | -| Search Things | ✓ | ✓ | -| Count Things | no | ✓ | -| Messages | ✓ | ✓ | -| Change notifications | ✓ | ✓ (SSEs) | -| Access control via Policy | ✓ | ✓ (v2 only) | - - -## Further aspects in which the interfaces differ - -| Criteria | Ditto Protocol over WebSocket | REST-like HTTP API | -|---------------------|---------------------------------|---------------------------| -| Programming language | Almost any web-oriented programming language, e.g. Java, JavaScript, .NET | Almost any programming language, e.g. Java, JavaScript, NodeJS, .NET, Python, C/C++ | -| Connection paradigm | Connection-oriented with an always open and persistent connection with only one-time handshake overhead for lowest latency and highest throughput | Connectionless protocol with lower permanent resource allocation on sporadic transactions | -| Channel security | WSS: WebSocket over Transport Layer Security | HTTPS: HTTP over Transport Layer Security | -| Message exchange pattern | Non-blocking request - asynchronous response | Blocking request - response | -| Authentication mechanism | User authentication using: HTTP BASIC Authentication, JSON Web Token (JWT) issued OpenID connect provider | User authentication using: HTTP BASIC Authentication, JSON Web Token (JWT) issued OpenID connect provider | +For a comprehensive overview of all Ditto APIs and interfaces, see the [API Overview](httpapi-overview.html). diff --git a/documentation/src/main/resources/pages/ditto/basic-auth-checkpermissions.md b/documentation/src/main/resources/pages/ditto/basic-auth-checkpermissions.md index f64e0e84d72..493c5819419 100644 --- a/documentation/src/main/resources/pages/ditto/basic-auth-checkpermissions.md +++ b/documentation/src/main/resources/pages/ditto/basic-auth-checkpermissions.md @@ -1,48 +1,102 @@ --- -title: Checking Permissions for Resources -keywords: permissions, authorization, resources, policy, checkPermissions -tags: [model] -permalink: basic-auth-checkpermissions.html +title: Checking Permissions for Resources +keywords: permissions, authorization, resources, policy, checkPermissions +tags: [model] +permalink: basic-auth-checkpermissions.html --- -The `/checkPermissions` endpoint allows clients to validate permissions for specified entities on various resources, verifying access rights as defined in Ditto's policies. +The `/checkPermissions` endpoint lets you verify whether the current user has specific permissions +on specific resources -- without modifying any data. -## Overview +{% include callout.html content="**TL;DR**: POST a JSON object to `/checkPermissions` listing the resources and +permissions you want to check. Ditto returns `true` or `false` for each one." type="primary" %} -The `/checkPermissions` endpoint is part of Ditto's HTTP API, enhancing its policy-based authorization system by enabling permission validation checks on resources without modifying them. -This functionality is valuable for UI-driven applications, where permissions checks can determine whether certain UI elements should be displayed or disabled based on the user’s access rights. +## When to use this -## Request Structure +Permission checks are useful for: -Submit a `POST` request with a JSON payload specifying entities, resources, and permissions: +* **UI applications** -- determine whether to show or disable buttons based on the user's access + rights +* **Pre-flight checks** -- verify that a batch operation will succeed before starting it +* **Debugging** -- confirm that a Policy grants the expected permissions + +## How it works + +Send a `POST` request to the `/checkPermissions` endpoint with a JSON body. Each key in the JSON +object names a check, and each value specifies the resource, entity, and required permissions: ```json { - "entity_name": { - "resource": "thing:/features/lamp/properties/on", - "entityId": "org.eclipse.ditto:some-thing-1", - "hasPermissions": ["READ"] - }, - "another_entity": { - "resource": "message:/features/lamp/inbox/messages/toggle", - "entityId": "org.eclipse.ditto:some-thing-2", - "hasPermissions": ["WRITE"] - } + "canReadLampState": { + "resource": "thing:/features/lamp/properties/on", + "entityId": "org.eclipse.ditto:some-thing-1", + "hasPermissions": ["READ"] + }, + "canToggleLamp": { + "resource": "message:/features/lamp/inbox/messages/toggle", + "entityId": "org.eclipse.ditto:some-thing-1", + "hasPermissions": ["WRITE"] + }, + "canEditPolicy": { + "resource": "policy:/", + "entityId": "org.eclipse.ditto:some-policy-1", + "hasPermissions": ["READ", "WRITE"] + } } ``` -## Fields -- entity_name: Identifier for the entity performing the action. -- resource: Path of the target resource, starting with thing:, message:, or policy: followed by a valid resource path. -- entityId: Unique identifier for the entity, such as a thingId or policyId, depending on the resource. -- hasPermissions: Array of required permissions, such as READ or WRITE. -## Response Structure -The response indicates permission status for each entity and resource, returning a JSON object mapping entities to true (authorized) or false (unauthorized) values. +### Request fields + +| Field | Description | +|-------|-------------| +| *(key)* | A name you choose to identify this check in the response | +| `resource` | The resource path to check. Starts with `thing:`, `message:`, or `policy:` followed by a valid resource path. | +| `entityId` | The ID of the entity (Thing ID or Policy ID, depending on the resource type) | +| `hasPermissions` | An array of permissions to check: `READ`, `WRITE`, and/or `EXECUTE` | + +## Response + +Ditto returns a JSON object mapping each check name to `true` (authorized) or `false` +(not authorized): ```json { - "entity_name": true, - "another_entity": false + "canReadLampState": true, + "canToggleLamp": true, + "canEditPolicy": false } ``` -This endpoint is especially useful for applications requiring quick permission validation for multiple entities across various resources. \ No newline at end of file + +## Example + +Check whether the authenticated user can read a Thing's temperature and send a reset message: + +```bash +curl -u ditto:ditto -X POST -H 'Content-Type: application/json' -d '{ + "readTemp": { + "resource": "thing:/features/temperature/properties/value", + "entityId": "com.example:sensor-1", + "hasPermissions": ["READ"] + }, + "sendReset": { + "resource": "message:/inbox/messages/reset", + "entityId": "com.example:sensor-1", + "hasPermissions": ["WRITE"] + } +}' 'http://localhost:8080/api/2/checkPermissions' +``` + +Response: + +```json +{ + "readTemp": true, + "sendReset": false +} +``` + +## Further reading + +* [Authentication & Authorization](basic-auth.html) -- how Ditto authenticates requests +* [Policies](basic-policy.html) -- define the permissions that this endpoint checks +* [HTTP API reference](http-api-doc.html) -- full API documentation diff --git a/documentation/src/main/resources/pages/ditto/basic-auth.md b/documentation/src/main/resources/pages/ditto/basic-auth.md index 70ed406806e..d61f71c2fc1 100644 --- a/documentation/src/main/resources/pages/ditto/basic-auth.md +++ b/documentation/src/main/resources/pages/ditto/basic-auth.md @@ -1,71 +1,92 @@ --- -title: Authentication and authorization +title: Authentication & Authorization keywords: auth, authentication, authorization, policies, policy, sso, single sign on tags: [model] permalink: basic-auth.html --- -You can integrate your solutions with Ditto +Ditto protects every API request with authentication (verifying identity) and authorization +(checking permissions). -* via the [HTTP API](http-api-doc.html) or -* via WebSocket. +{% include callout.html content="**TL;DR**: Ditto authenticates requests via pre-authentication (for example, nginx +basic auth) or JWT tokens from OpenID Connect providers. Authorization is enforced through +[Policies](basic-policy.html) that map authenticated subjects to fine-grained permissions." type="primary" %} -On all APIs Ditto protects functionality and data by using +## Authentication -* **Authentication** to make sure the requester is the one he/she claims to be, -* **Authorization** to make sure the requester is allowed to see, use or change the information he wants to access. +Every request to Ditto's [HTTP API](http-api-doc.html) or WebSocket API must carry valid +credentials. Ditto supports two authentication mechanisms: -## Authentication +### Pre-authentication + +An HTTP reverse proxy (like nginx) in front of Ditto authenticates the user and passes the +verified identity to Ditto. The default Docker deployment uses nginx with HTTP Basic Authentication. + +See the [pre-authentication configuration guide](operating-authentication.html#pre-authentication) +for setup details. + +### JWT (OpenID Connect) -User authentication at the HTTP API +Ditto accepts JWT tokens +from Google and other OpenID Connect providers. You configure trusted providers in the Ditto +installation, and Ditto validates the token signature and claims on each request. -A user who calls the HTTP API can be authenticated using two mechanisms: +See the [OpenID Connect configuration guide](operating-authentication.html#openid-connect) for +setup details. -* Pre-authentication by an HTTP reverse proxy in front of Ditto, e.g. doing HTTP BASIC Authentication by providing - username and password as [documented in the installation/operation guide](installation-operating.html#pre-authentication). -* A JWT issued by Google or other - OpenID Connect providers as [documented in the installation/operation guide](installation-operating.html#openid-connect). +## Authenticated subjects -### Authenticated subjects +Every request processed by Ditto carries one or more **authenticated subjects**. A subject +identifies the requester and takes the form `:`: -Every request to one of Ditto's API is done in scope of already authenticated subjects. -This authentication may be provided via nginx (like mentioned [above](#authentication)), a -JWT or in a connection via the -configured `authorizationContext` in scope of the connection's [authorization](basic-connections.html#authorization). +| Example | Source | +|---------|--------| +| `nginx:ditto` | nginx pre-authentication | +| `google:1234567890` | Google JWT | +| `my-keycloak:user-uuid` | Custom OpenID Connect provider | -For each of the possibilities of authenticating subjects, the [command](basic-signals-command.html) or -[message](basic-messages.html) processed by Ditto will contain one or more of the "authenticated subjects" which e.g. -might be user IDs. +These subjects are matched against [Policy](basic-policy.html) entries to determine what the +requester can read, write, or execute. +For connections, the subjects come from the connection's configured +[authorization context](basic-connections.html#authorization). -### Single sign-on (SSO) +## Single sign-on (SSO) -By configuring an arbitrary OpenID Connect provider (as mentioned above) it is possible for Ditto to participate in SSO -for the following browser based requests: -* [HTTP API](httpapi-overview.html) invocations - * sending along a JWT token as `Authorization` header with `Bearer` value -* Establishing a [WebSocket](httpapi-protocol-bindings-websocket.html) connection for bidirectional communication with - Ditto via [Ditto Protocol](protocol-overview.html) JSON messages - * sending along a JWT token as `Authorization` header with `Bearer` value (recommended) - * sending along a JWT token as query parameter `access_token` (use only if the websocket client does not - support setting http headers e.g. plain WebSocket API of browsers) -* Opening a [Server sent event](httpapi-sse.html) connection in order to receive change notifications of twins in the +By configuring an OpenID Connect provider, Ditto participates in single sign-on flows for +browser-based applications: + +* **HTTP API** -- send the JWT as an `Authorization: Bearer ` header +* **WebSocket** -- send the JWT as an `Authorization: Bearer ` header (recommended), or as + the `access_token` query parameter (use only if your WebSocket client does not support custom + headers, for example the plain browser WebSocket API) +* **Server Sent Events** -- pass `withCredentials: true` when creating the `EventSource` in the browser - * passing the `withCredentials: true` option when creating the SSE in the browser ## Authorization -Authorization is implemented with a Policy -(in API version 2). +Once Ditto identifies the authenticated subjects, it checks them against the +Policy +attached to the target resource. The Policy determines whether the subjects have the required +permissions (`READ`, `WRITE`, or `EXECUTE`) on the requested resource path. + +See [Policies](basic-policy.html) for the full authorization model. + +### Authorization context in DevOps commands + +When using [DevOps commands](operating-devops.html), you pass an +`authorizationContext` that must contain a subject known to Ditto's authentication layer. -Please find details at [Policies](basic-policy.html). +For the Docker quickstart deployment, use `nginx:ditto` -- this makes DevOps commands execute +with the same identity as HTTP requests from the `ditto` user: -### Authorization Context in DevOps Commands +```text +nginx:ditto +``` -An `authorizationContext` which is passed to [DevOps Commands](installation-operating.html#devops-commands) needs -to be a subject known to Ditto's authentication. In the simplest case, it's `nginx:{username}` where `{username}` is a user -that is configured for basic auth in the included nginx's `nginx.htpasswd` file (where the `nginx:` prefix comes from). +## Further reading -If you are using the provided docker quickstart example from [Getting Started](installation-running.html) you -can simply use `nginx:ditto`, then the commands that are passed from the connection are executed as if they -were issued via HTTP from the user `ditto`. +* [Policies](basic-policy.html) -- define fine-grained access control +* [Checking Permissions](basic-auth-checkpermissions.html) -- validate permissions without + modifying data +* [Installation & Operation](installation-operating.html) -- configure authentication providers diff --git a/documentation/src/main/resources/pages/ditto/basic-changenotifications.md b/documentation/src/main/resources/pages/ditto/basic-changenotifications.md index 0a410343694..c5a7dbac22a 100644 --- a/documentation/src/main/resources/pages/ditto/basic-changenotifications.md +++ b/documentation/src/main/resources/pages/ditto/basic-changenotifications.md @@ -1,90 +1,89 @@ --- -title: Change notifications +title: Change Notifications keywords: change, event, feature, notification, thing, filtering, rql, push, subscribe, consume, enrich, extra tags: [model, rql] permalink: basic-changenotifications.html --- -[Signals](basic-signals.html) already described what an [Event](basic-signals-event.html) in Ditto is. -Events are emitted after an entity (either a **digital twin** or an actual device) was changed. +Change notifications deliver [events](basic-signals-event.html) to your application whenever a +digital twin or device state changes. -At the Ditto API there are different ways for getting notified of such events: -* Via the [WebSocket API](httpapi-protocol-bindings-websocket.html) a WebSocket client gets all Events the authenticated subject - (e.g. a user) is [authorized](basic-auth.html) to receive as [Ditto Protocol](protocol-overview.html) messages. -* Via [HTTP SSEs](httpapi-sse.html) a consumer of the SSE `EventSource` gets all Events the authenticated subject - (e.g. a user) is [authorized](basic-auth.html) to receive directly in the format of the changed entity - (e.g. as [Thing JSON](basic-thing.html#model-specification) format). -* Via an established [connection](basic-connections.html) in the [connectivity](connectivity-overview.html) service +{% include callout.html content="**TL;DR**: Subscribe to change notifications via WebSocket, Server Sent Events (SSE), +or connections. Filter by namespace or RQL expression to receive only the events you care about." type="primary" %} +## How to receive change notifications -## Filtering +Ditto publishes events through three channels. Each delivers events to authenticated subjects that +have the required [authorization](basic-auth.html): -In order to not get all of the events an [authenticated subject](basic-auth.html#authenticated-subjects) -(e.g. a user added in nginx) is allowed to see, but to filter for specific criteria, -events may be filtered on the Ditto backend side before they are sent to an event receiver. +| Channel | Format | Use case | +|---------|--------|----------| +| [WebSocket API](httpapi-protocol-bindings-websocket.html) | [Ditto Protocol](protocol-overview.html) messages | Bidirectional communication from browser or backend clients | +| [HTTP SSE](httpapi-sse.html) | Changed entity JSON (for example, [Thing JSON](basic-thing.html#model-specification)) | Lightweight, read-only streaming in browsers | +| [Connections](basic-connections.html) | Configurable via [connectivity](connectivity-overview.html) | Server-to-server integration with message brokers | -The above mentioned different APIs provide their own mechanisms on how to define such filters, but they all share the -common functionality of based on which information events may be filtered. +## Filtering -{% include note.html content="All filters are specified in a URL query format, therefore their values should be URL - encoded before sending them to the backend. The equal (=) and the ampersand (&) character must be encoded in any RQL - filter!" %} +You can filter events on the Ditto backend before they reach your application. Each API provides +its own mechanism for specifying filters, but all support the same filter types. -### By namespaces +{% include note.html content="All filters are specified in URL query format, so their values should be URL-encoded. +The equal sign (=) and ampersand (&) must be encoded in any RQL filter." %} -Filtering may be done based on a namespace name. Each Ditto [Thing](basic-thing.html) has an ID containing a namespace -(see also the conventions for a [Thing ID](basic-thing.html#thing-id)). +### Filter by namespace -By providing the `namespaces` filter, a comma separated list of which namespaces to include in the result, only Things -in namespaces of interest are considered and thus only events of these Things are emitted at the API. +Provide a comma-separated list of namespaces to receive events only from Things in those +namespaces: -For example, one would only subscribe for events occurring in 2 specific namespaces by defining: -``` +```text namespaces=org.eclipse.ditto.one,org.eclipse.ditto.two ``` -### By RQL expression +### Filter by RQL expression -If filtering by namespaces is not sufficient, Ditto also allows to provide an [RQL expression](basic-rql.html) -specifying: +For more granular control, use an [RQL expression](basic-rql.html) to filter based on: -* a Thing payload based condition determining which events should be emitted and which should not -* a filter based on the fields of the [Ditto Protocol](protocol-specification.html) message which should be filtered, - e.g.: - * using the `topic:action` placeholder as query property, filtering for lifecycle events (`created`, `deleted`) - and other filter options on the [Ditto Protocol topic](protocol-specification-topic.html) can be done - * using the `resource:path` placeholder as query property, filtering based on the affected - [Ditto Protocol path](protocol-specification.html#path) of a Ditto Protocol message can be done - * for all supported placeholders, please refer to the - [placeholders documentation](basic-placeholders.html#scope-rql-expressions-when-filtering-for-ditto-protocol-messages) +* **Thing data** -- filter on the modified values in the event payload +* **Ditto Protocol fields** -- filter on message metadata using + [placeholders](basic-placeholders.html#scope-rql-expressions-when-filtering-for-ditto-protocol-messages): + * `topic:action` -- filter for lifecycle events (`created`, `deleted`) + * `resource:path` -- filter by the affected [resource path](protocol-specification.html#path) -{% include note.html content="This filter is *by default* applied on the modified data of a Thing.
    - Data which was not changed will only be considered when it was - [enriched via \"extraFields\"](basic-enrichment.html)." %} +{% include note.html content="The RQL filter applies to the *modified* data by default. Unchanged data is only +considered when it has been [enriched via extraFields](basic-enrichment.html)." %} -This provides the opportunity to formulate filters like the following: +### Examples -#### Examples +Only emit events when `count` changes to a value greater than 42: -Only emit events when attribute "count" was changed to a value greater than 42: -``` +```text filter=gt(attributes/count,42) ``` -Only emit events for Things starting with myThing when a feature "lamp" was modified: -``` +Only emit events for Things starting with "myThing" when the "lamp" feature changes: + +```text filter=and(like(thingId,"org.eclipse.ditto:myThing*"),exists(features/lamp)) ``` -Only emit events when the attribute "manufacturer" was changed to starting with "ACME & Sons". -The `&` must be escaped in that case: -``` +Only emit events when `manufacturer` starts with "ACME & Sons" (note the encoded `&`): + +```text filter=like(attributes/manufacturer,"ACME %26 Sons*") ``` Only emit events for Thing creation and deletion: -``` + +```text filter=and(in(topic:action,'created','deleted'),eq(resource:path,'/')) ``` -You get the idea of how mighty this becomes by utilizing Ditto's [RQL expressions](basic-rql.html). +See the full [RQL expression reference](basic-rql.html) for the complete query language. + +## Further reading + +* [Signal Enrichment](basic-enrichment.html) -- add extra context (like attributes) to events +* [Signals & Communication Pattern](basic-signals.html) -- understand the signal types +* [WebSocket Protocol Binding](httpapi-protocol-bindings-websocket.html) -- subscribe via WebSocket +* [Server Sent Events](httpapi-sse.html) -- subscribe via SSE +* [Connections](basic-connections.html) -- subscribe via managed connections diff --git a/documentation/src/main/resources/pages/ditto/basic-conditional-requests.md b/documentation/src/main/resources/pages/ditto/basic-conditional-requests.md index 3a49e99f210..bbfab54bdaa 100644 --- a/documentation/src/main/resources/pages/ditto/basic-conditional-requests.md +++ b/documentation/src/main/resources/pages/ditto/basic-conditional-requests.md @@ -5,41 +5,41 @@ tags: [protocol, http, rql] permalink: basic-conditional-requests.html --- -Ditto already supports [Conditional Requests](httpapi-concepts.html#conditional-requests) as defined in [RFC-7232](https://tools.ietf.org/html/rfc7232) -where the `If-Match` and `If-None-Match` headers can be used to specify if a request should be applied or not. -With the `condition` header it is possible to specify a condition based on the state of the actual thing. -It is possible to combine both headers within one request. If you use both headers keep in mind that the ETag header is evaluated first. +Conditional requests let you apply commands to Things only when specific conditions about the Thing's current state are met. + +{% include callout.html content="**TL;DR**: Use the `condition` header with an RQL expression to make updates conditional on the Thing's current state. You can also use `If-Match`/`If-None-Match` headers for ETag-based conditions, and `live-channel-condition` for automatic twin/live switching." type="primary" %} + +## Overview + +Ditto supports [conditional requests as defined in RFC-7232](https://tools.ietf.org/html/rfc7232) using `If-Match` and `If-None-Match` headers. Additionally, the `condition` header lets you specify conditions based on the current state of the persisted twin. + +You can combine both header types in one request. When you do, Ditto evaluates the ETag header first. ## Defining conditions -Ditto supports retrieving, modifying, deleting and sending messages to/from things based on specific conditions of the -current persisted twin state. -For example, if you want to update the value of an attribute, but only if the current attribute value is not already 42, -you can specify a condition: +You define conditions using [RQL expressions](basic-rql.html). A condition specifies that Ditto should apply the request only if the expression evaluates to `true` against the current twin state. -``` +For example, to update an attribute only if the current value is not already 42: + +```text PUT /api/2/things/org.eclipse.ditto:foo1/attributes/value?condition=ne(attributes/value,42) 42 ``` -Conditions are based on [RQL expressions](basic-rql.html) and define that a request should be applied to a thing -only if the condition is met. It is possible to use any field in your thing to define a condition. -E.g. you can use a timestamp in case you only want to change the state of the thing, if the provided value -is newer than in the last state of the thing. +You can reference any field in the Thing to build a condition. This is useful for timestamp-based updates where you only want to apply a change if the incoming value is newer than the stored one. -* If the condition specified in the request is fulfilled, the thing will be updated and an event will be emitted. -* If the condition specified in the request is not fulfilled, the thing is not modified, and no [event/change notification](basic-changenotifications.html) is emitted. +- If the condition is met, Ditto updates the Thing and emits an event. +- If the condition is not met, Ditto does not modify the Thing and emits no [event/change notification](basic-changenotifications.html). -Conditional requests are supported by HTTP API, WebSocket, Ditto protocol and Ditto Java Client. +Conditional requests work with HTTP API, WebSocket, Ditto Protocol, and the Ditto Java Client. ### Permissions for conditions -READ permission is necessary on the resource specified in the condition, otherwise, the request will fail. +You need READ permission on the resource referenced in the condition. Otherwise, the request fails. ## Examples -In this part, we will show how to use conditional updates via HTTP API, Ditto protocol, and Ditto Java Client. -The below examples assume that we have the following thing state: +The following examples assume this Thing state: ```json { @@ -60,36 +60,29 @@ The below examples assume that we have the following thing state: } ``` -In our example we want to update the **temperature** value, but only if the current value is newer than the already stored one. -To express this condition, we use the _lastModified_ field in the temperature feature. - -In the following sections, we will show how request the conditional update via HTTP API, Ditto protocol, -and Ditto Java Client which is based on the WebSocket protocol. +The goal: update the temperature value only if the incoming value is newer than the stored one, using the `lastModified` field. ### HTTP API -Using the HTTP API it is possible to specify the condition via query parameter +Specify the condition as a query parameter: -``` +```bash curl -X PUT -H 'Content-Type: application/json' /api/2/things/org.eclipse.ditto:fancy-thing/features/temperature/properties/value?condition=gt(features/temperature/properties/lastModified,'2021-08-10T15:10:02.592Z') -d 19.26 ``` -or via HTTP header +Or as an HTTP header: -``` +```bash curl -X PUT -H 'Content-Type: application/json' -H 'condition: gt(features/temperature/properties/lastModified,"2021-08-10T15:10:02.592Z")' /api/2/things/org.eclipse.ditto:fancy-thing/features/temperature/properties/value -d 19.26 ``` -### Ditto protocol - -The Ditto protocol supports also conditional updates. -This is an example how to do a conditional update via [Ditto Protocol](protocol-specification.html) message: +### Ditto Protocol ```json { "topic": "org.eclipse.ditto/fancy-thing/things/twin/commands/modify", "headers": { - "condition": "gt(features/temperature/properties/lastModified,2021-08-10T15:10:02.592Z)" + "condition": "gt(features/temperature/properties/lastModified,2021-08-10T15:10:02.592Z)" }, "path": "/features/temperature/properties/value", "value": 19.26 @@ -98,13 +91,10 @@ This is an example how to do a conditional update via [Ditto Protocol](protocol- ### Ditto Java Client -The third option to use conditional updates is the ditto-client. -The following code snippet demonstrates how to achieve this. - ```java final Option option = Options.condition("gt(features/temperature/properties/lastModified,\"2021-08-10T15:10:02.592Z\")") - + client.twin().forFeature(ThingId.of("org.eclipse.ditto:fancy-thing"), "temperature") .putProperty("value", 42, option) .whenComplete((ununsed, throwable) -> { @@ -118,78 +108,51 @@ client.twin().forFeature(ThingId.of("org.eclipse.ditto:fancy-thing"), "temperatu ## Live channel condition -Ditto also supports retrieving thing data with an automatic approach for switching between -[twin](protocol-twinlive.html#twin) and [live](protocol-twinlive.html#live) channel. +Ditto supports automatic switching between [twin](protocol-twinlive.html#twin-channel) and [live](protocol-twinlive.html#live-channel) channels based on a condition. -Conditions are defined with RQL as described [before](#defining-conditions). -If a condition is matched, the Thing data is retrieved from the device itself. +Define the condition with RQL. If it matches, Ditto retrieves data from the device itself: -Example: retrieve data from the device itself if a certain attribute is configured at the twin: -``` +```text GET .../things/{thingId}?live-channel-condition=eq(attributes/useLiveChannel,true) ``` -Example: retrieve data from the device itself if the last modification timestamp is behind a specified timestamp: -``` +```text GET .../things/{thingId}?live-channel-condition=lt(_modified,"2021-12-24T12:23:42Z") ``` -### Live channel condition headers +### Timeout strategy -Additionally, a strategy to handle timeouts for retrieving live thing data can be specified with the header -`live-channel-timeout-strategy`. -The header value holds a **strategy** what to do in case a timeout (can also be specified as header) was encountered. +The `live-channel-timeout-strategy` header controls what happens when the device does not respond within the timeout: -* If the value cannot be retrieved live from the device itself during the specified timeout, the request will - `fail` (which is the default strategy if not specified otherwise) with a status code 408. -* Alternatively, if `use-twin` was defined as the `live-channel-timeout-strategy` strategy, the request will fall back - to the persisted twin and return the latest value stored in the digital twin. +- `fail` (default): return status code `408` +- `use-twin`: fall back to the persisted twin data +### Response headers -### Live channel condition response headers +The response includes two headers indicating which channel was used: -The response includes two additional headers to indicate which channel was used to retrieve the thing data: +- `live-channel-condition-matched`: `true` or `false` +- `channel`: `twin` or `live` -* `live-channel-condition-matched` – value could be `true` or `false` and states whether the passed live-channel-condition was a match or not -* `channel` – value could be `twin` or `live` and defines which channel was the origin of the returned data. - -In line with the procedure described above on how to retrieve live data directly from a device, a new type of -pre-configured payload mapping, namely [UpdateTwinWithLiveResponse](connectivity-mapping.html#updatetwinwithliveresponse-mapper) -was introduced. - -Upon activation, the digital twin stored in Eclipse Ditto will implicitly be updated with the latest data from the -_live response_ sent by the device. +You can use the [UpdateTwinWithLiveResponse](connectivity-mapping.html#updatetwinwithliveresponse-mapper) payload mapper to automatically update the digital twin with live response data. ## Path-specific conditions -In addition to global conditions, Ditto supports since version 3.8.0 path-specific conditions for -[merge operations](protocol-specification-things-merge.html) using the `merge-thing-patch-conditions` header. -This feature allows you to apply different [RQL conditions](basic-rql.html) to different parts of a merge patch, -enabling fine-grained control over which parts of the patch are applied based on the current state of the Thing. - -The `merge-thing-patch-conditions` header contains a JSON object where each key represents a JSON pointer path and each -value is an RQL expression that must evaluate to `true` for that path to be included in the merge. - -The provided key (JSON pointer path) is relative to the modification `path` of the merge command. -When e.g. providing a command which merges at path `/`, provided key to target a `value` property in a `temperature` -feature would have the format `features/temperature/properties/value`. -Issuing a merge command to merge at the feature level `features/temperature` would require the key to be `properties/value` -instead. +Since Ditto 3.8.0, you can apply different conditions to different parts of a [merge operation](protocol-specification-things.html#merge-commands) using the `merge-thing-patch-conditions` header. -* If a path-specific condition is fulfilled, that part of the merge patch will be applied. -* If a path-specific condition is not fulfilled, that part of the merge patch will be skipped. -* Paths without conditions will always be applied. +The header contains a JSON object where each key is a JSON pointer path (relative to the merge command's `path`) and each value is an RQL expression. Paths without conditions are always applied. -Path-specific conditions are supported by HTTP API, WebSocket, Ditto protocol and Ditto Java Client. +- If a path-specific condition is met, that part of the merge patch is applied. +- If a path-specific condition is not met, that part is skipped. +- Paths without conditions are always applied. ### Permissions for path-specific conditions -READ permission is necessary on all resources referenced in the path-specific conditions, otherwise, the request will fail. +You need READ permission on all resources referenced in the conditions. ### Examples -This part shows how to use path-specific conditions via HTTP API, Ditto protocol, and Ditto Java Client. -The below examples assume that there is a thing with the following thing state: +Given this Thing state: ```json { @@ -227,9 +190,7 @@ The below examples assume that there is a thing with the following thing state: #### HTTP API -Using the HTTP API it is possible to specify path-specific conditions via HTTP header `merge-thing-patch-conditions`: - -```shell +```bash curl -X PATCH -H 'Content-Type: application/merge-patch+json' \ -H 'merge-thing-patch-conditions: {"features/temperature/properties/value": "gt(features/temperature/properties/value,20)", "features/humidity/properties/value": "lt(features/humidity/properties/value,80)"}' \ http://localhost:8080/api/2/things/org.eclipse.ditto:fancy-thing \ @@ -237,24 +198,21 @@ curl -X PATCH -H 'Content-Type: application/merge-patch+json' \ ``` In this example: -- `temperature` will only be updated if the current temperature is greater than 20 (condition fails, so temperature won't be updated) -- `humidity` will only be updated if the current humidity is less than 80 (condition fails, so humidity won't be updated) -- `status` will always be updated (no condition specified) +- `temperature` is not updated (15 is not > 20) +- `humidity` is not updated (90 is not < 80) +- `status` is always updated (no condition) -The same call could also be done on `/features` path with adjusted condition keys. -Notice that the conditions are still relative to the root of the thing, only the key paths are adjusted accordingly: +The same call can also target the `/features` path with adjusted condition keys. +Notice that conditions are still relative to the root of the Thing, only the key paths are adjusted: -```shell +```bash curl -X PATCH -H 'Content-Type: application/merge-patch+json' \ -H 'merge-thing-patch-conditions: {"temperature/properties/value": "gt(features/temperature/properties/value,20)", "humidity/properties/value": "lt(features/humidity/properties/value,80)"}' \ http://localhost:8080/api/2/things/org.eclipse.ditto:fancy-thing/features \ -d '{"temperature": {"properties": {"value": 25}}, "humidity": {"properties": {"value": 60}}, "status": {"properties": {"state": "updated"}}}' ``` -#### Ditto protocol - -The Ditto protocol supports also path-specific conditions for merge operations. -This is an example how to do a conditional merge via [Ditto Protocol](protocol-specification.html) message: +#### Ditto Protocol ```json { @@ -268,21 +226,9 @@ This is an example how to do a conditional merge via [Ditto Protocol](protocol-s "lastMaintenance": "2023-01-20T15:00:00Z" }, "features": { - "temperature": { - "properties": { - "value": 25 - } - }, - "humidity": { - "properties": { - "value": 60 - } - }, - "status": { - "properties": { - "state": "updated" - } - } + "temperature": { "properties": { "value": 25 } }, + "humidity": { "properties": { "value": 60 } }, + "status": { "properties": { "state": "updated" } } } } } @@ -290,9 +236,6 @@ This is an example how to do a conditional merge via [Ditto Protocol](protocol-s #### Ditto Java Client -The third option to use path-specific conditions is the [ditto-client](client-sdk-java.html). -The following code snippet demonstrates how to achieve this. - ```java Map cond = new HashMap<>(); cond.put("features/temperature/properties/value", "gt(features/temperature/properties/value,20)"); @@ -308,19 +251,13 @@ client.twin().forId(ThingId.of("org.eclipse.ditto:fancy-thing")) .set("features", JsonObject.newBuilder() .set("temperature", JsonObject.newBuilder() .set("properties", JsonObject.newBuilder() - .set("value", 25) - .build()) - .build()) + .set("value", 25).build()).build()) .set("humidity", JsonObject.newBuilder() .set("properties", JsonObject.newBuilder() - .set("value", 60) - .build()) - .build()) + .set("value", 60).build()).build()) .set("status", JsonObject.newBuilder() .set("properties", JsonObject.newBuilder() - .set("state", "updated") - .build()) - .build()) + .set("state", "updated").build()).build()) .build()) .build(), condOption) .whenComplete((unused, throwable) -> { @@ -332,63 +269,68 @@ client.twin().forId(ThingId.of("org.eclipse.ditto:fancy-thing")) }); ``` -### Configuration for path-specific conditions - -When using path-specific conditions, it's possible that all parts of a merge payload are filtered out, resulting in -empty JSON objects being persisted to the database. -Ditto provides a configuration option to avoid persisting events in case that only empty objects are left after applying -the conditions. - -#### Empty object removal +### Empty object removal configuration **Configuration option:** `MERGE_REMOVE_EMPTY_OBJECTS_AFTER_PATCH_CONDITION_FILTERING` -**Default behavior:** `false` (empty objects are preserved for backward compatibility) +**Default**: `false` (empty objects preserved for backward compatibility). -**When enabled:** Empty objects created by patch condition filtering are removed recursively, preventing unnecessary -database operations and saving database I/O. +When enabled, Ditto recursively removes empty JSON objects created by condition filtering. This +prevents unnecessary database operations when all parts of a merge patch are filtered out. -**Example scenario - Complete filtering:** +**Example scenario** -- given this Thing state: ```json -// Current Thing state { "features": { - "temp": {"properties": {"value": 15}}, // Current: 15 - "hum": {"properties": {"value": 70}} // Current: 70 + "temp": { "properties": { "value": 15 } }, + "hum": { "properties": { "value": 70 } } } } +``` -// Merge payload +Merge payload: + +```json { "features": { - "temp": {"properties": {"value": 25}}, - "hum": {"properties": {"value": 60}} + "temp": { "properties": { "value": 25 } }, + "hum": { "properties": { "value": 60 } } } } +``` + +Patch conditions (both evaluate to `false`): -// Patch conditions (both will evaluate to false) +```json { - "features/temp/properties/value": "gt(features/temp/properties/value,30)", // 15 > 30 = false - "features/hum/properties/value": "lt(features/hum/properties/value,50)" // 70 < 50 = false + "features/temp/properties/value": "gt(features/temp/properties/value,30)", + "features/hum/properties/value": "lt(features/hum/properties/value,50)" } +``` + +**Result with configuration disabled** (default) -- empty objects preserved, database operation +still occurs: -// Result with configuration disabled (default) +```json { "features": { - "temp": {"properties": {}}, // Empty object preserved - "hum": {"properties": {}} // Empty object preserved + "temp": { "properties": {} }, + "hum": { "properties": {} } } } -// → Database operation still occurs, empty objects stored +``` -// Result with configuration enabled -{} // Completely empty payload -// → Database operation skipped entirely, no storage, no event emission, no new revision +**Result with configuration enabled** -- completely empty payload, database operation skipped +entirely (no storage, no event emission, no new revision): + +```json +{} ``` -For configuration details, see [Merge operations configuration](installation-operating.html#merge-operations-configuration). +For configuration details, see [Merge operations configuration](operating-configuration.html#merge-operations-configuration). -## Further reading on RQL expressions +## Further reading -See [RQL expressions](basic-rql.html). +- [RQL expressions](basic-rql.html) -- query language reference +- [Things specification](protocol-specification-things.html) -- Thing commands including merge diff --git a/documentation/src/main/resources/pages/ditto/basic-connections.md b/documentation/src/main/resources/pages/ditto/basic-connections.md index daedb7f27c7..493d1b73ab6 100644 --- a/documentation/src/main/resources/pages/ditto/basic-connections.md +++ b/documentation/src/main/resources/pages/ditto/basic-connections.md @@ -1,41 +1,39 @@ --- -title: Connections -keywords: connection, connectivity, mapping, connection, integration, placeholder, qos, at least once, delivery, guarantee +title: Connections Overview +keywords: connection, connectivity, mapping, integration, placeholder, qos, at least once, delivery, guarantee tags: [connectivity] permalink: basic-connections.html --- -## Connection model +Connections let you integrate Ditto with external messaging systems so that devices can exchange data with their digital twins through protocols like AMQP, MQTT, HTTP, and Kafka. - {% - include note.html content="To get started with connections right away, consult the - [Manage connections](connectivity-manage-connections.html) page. " - %} +{% include callout.html content="**TL;DR**: A connection is a managed communication channel between Ditto and an external system. You configure sources to consume inbound messages and targets to publish outbound messages, with authorization, enforcement, and payload mapping applied automatically." type="primary" %} -You can integrate your Ditto instance with external messaging services such as -[Eclipse Hono](https://eclipse.org/hono/), a [RabbitMQ](https://www.rabbitmq.com/) broker or an -[Apache Kafka](https://kafka.apache.org/) broker via custom "connections". +## Overview -Additionally, you may invoke foreign HTTP endpoints by using the -[HTTP connection type](connectivity-protocol-bindings-http.html). +You integrate your Ditto instance with external messaging services -- such as +[Eclipse Hono](https://eclipse.org/hono/), [RabbitMQ](https://www.rabbitmq.com/), +[Apache Kafka](https://kafka.apache.org/), or any HTTP endpoint -- by creating connections. -A connection represents a communication channel for the exchange of messages between any service and Ditto. -It requires a transport protocol, which is used to transmit [Ditto Protocol](protocol-overview.html) messages. -Ditto supports one-way and two-way communication over connections. This enables consumer/producer scenarios -as well as fully-fledged command and response use cases. Nevertheless, those options might be limited by -the transport protocol or the other endpoint's capabilities. - -All connections are configured and supervised via Ditto's -[Connectivity service](architecture-services-connectivity.html). The following model defines the connection itself: +A connection represents a communication channel that uses a transport protocol to transmit +[Ditto Protocol](protocol-overview.html) messages. Ditto supports one-way and two-way communication, +enabling consumer/producer scenarios as well as full command-and-response workflows. -{% include docson.html schema="jsonschema/connection.json" %} +All connections are configured and supervised by Ditto's +[Connectivity service](architecture-services-connectivity.html). + +To create and manage connections, use the [HTTP API](connectivity-manage-connections.html) or +[DevOps piggyback commands](connectivity-manage-connections-piggyback.html). +## Connection model + +The following schema defines the connection model: -### Connection types +{% include docson.html schema="jsonschema/connection.json" %} -The top design priority of this model is to be as generic as possible, while still allowing protocol specific -customizations and tweaks. This enables the implementations of different customizable connection types, and support -for custom payload formats. Currently, the following connection types are supported: +## Supported connection types + +Ditto supports these connection types: * [AMQP 0.9.1](connectivity-protocol-bindings-amqp091.html) * [AMQP 1.0](connectivity-protocol-bindings-amqp10.html) @@ -44,144 +42,105 @@ for custom payload formats. Currently, the following connection types are suppor * [HTTP 1.1](connectivity-protocol-bindings-http.html) * [Kafka 2.x](connectivity-protocol-bindings-kafka2.html) -The `sources` and `targets` address formats depends on the `connectionType` and has therefore `connectionType` -specific limitations. Those are documented with the corresponding protocol bindings. +The format of `sources` and `targets` addresses depends on the `connectionType` and is documented +in each protocol binding page. + +## Sources -### Sources +Sources consume messages **from** external systems. Inbound messages can be: -Sources are used to connect to message brokers / external systems in order to consume messages **from them**. +* [Commands](basic-signals-command.html) +* [Messages](basic-messages.html) +* [Live commands/responses/events](protocol-twinlive.html) +* [Acknowledgements](protocol-specification-acks.html) -Source messages can be of the following type: -* [commands](basic-signals-command.html) -* [messages](basic-messages.html) -* [live commands/responses/events](protocol-twinlive.html) -* [acknowledgements](protocol-specification-acks.html) +A source contains: -Sources contain: -* several addresses (depending on the [connection type](#connection-types) those are interpreted differently, - e.g. as queues, topics, etc.), -* a consumer count defining how many consumers should be attached to each source address, -* an authorization context (see [authorization](#authorization)) specifying which - [authorization subject](basic-policy.html#subjects) is used to authorize messages from the source, -* enforcement information that allows filtering the messages that are consumed in this source, -* [acknowledgement requests](basic-acknowledgements.html#requesting-acks) this source requires in order - to ensure QoS 1 ("at least once") processing of consumed messages before technically acknowledging them to the channel, -* declared labels of [acknowledgements](protocol-specification-acks.html) the source is allowed to send, -* [header mapping](connectivity-header-mapping.html) for mapping headers of source messages to internal headers, and -* a reply-target to configure publication of any responses of incoming commands. +* **addresses** -- interpreted as queues, topics, etc. depending on the [connection type](#supported-connection-types) +* **consumerCount** -- how many consumers attach to each address +* **authorizationContext** -- [authorization subjects](basic-policy.html#subjects) used to authorize inbound messages (see [Authorization](#authorization)) +* **enforcement** -- filters to verify that a device only modifies its own digital twin +* **acknowledgementRequests** -- controls [QoS 1 processing](#source-acknowledgement-requests) +* **declaredAcks** -- labels of [acknowledgements](protocol-specification-acks.html) this source may send +* **headerMapping** -- maps external headers to internal headers (see [Header mapping](connectivity-header-mapping.html)) +* **replyTarget** -- where to publish responses to incoming commands -#### Source enforcement +### Source enforcement -Messages received from external systems are mapped to Ditto internal format, either by applying some custom mapping or -the default mapping for [Ditto Protocol](protocol-overview.html) messages. +By default, Ditto does not verify whether the device identity in an inbound message matches the +targeted thing. You can add enforcement to ensure a device only modifies its own digital twin. -During this mapping the digital twin of the device is determined i.e. -which thing is accessed or modified as a result of the message. By default, no sanity check is done if this target -thing corresponds to the device that originally sent the message. In some use cases this might be valid, but -in other scenarios you might want to enforce that a device only sends data to its digital twin. -Note that this could also be achieved by assigning a specific policy to each device and use [placeholders](#placeholders) -in the authorization subject, but this can get cumbersome to maintain for a large number of devices. +Enforcement requires that the external system provides a verified device identity (for example, in a +message header). -With an enforcement, you can use a single policy for all devices -and still make sure that a device only modifies its associated digital twin. Enforcement is only feasible if the message -contains the verified identity of the sending device (e.g. in a message header). This verification has to be done by the -external system e.g. by properly authenticating the devices and providing the identity in the messages sent to Ditto. +The enforcement configuration has two fields: -The enforcement configuration consists of two fields: -* `input`: Defines where device identity is extracted. -* `filters`: Defines the filters that are matched against the input. At least one filter must match the input value, -otherwise the message is rejected. +* `input` -- where the device identity is extracted from +* `filters` -- patterns matched against the input; at least one must match or the message is rejected -The following placeholders are available for the `input` field: +**Placeholders for `input`:** | Placeholder | Description | Example | |-----------|-------|---------------| -| `{%raw%}{{ header: }}{%endraw%}` | Any header from the message received via the source (case-insensitive). | `{%raw%}{{header:device_id }}{%endraw%}` | -| `{%raw%}{{ source:address }}{%endraw%}` | The address on which the message was received. | devices/sensors/temperature1 | - +| `{%raw%}{{ header: }}{%endraw%}` | Any header from the received message (case-insensitive) | `{%raw%}{{header:device_id }}{%endraw%}` | +| `{%raw%}{{ source:address }}{%endraw%}` | The address the message was received on | devices/sensors/temperature1 | -The following placeholders are available for the `filters` field: +**Placeholders for `filters`:** | Placeholder | Description | Example | |-----------|-------|---------------| -| `{%raw%}{{ thing:id }}{%endraw%}` | Full ID composed of ''namespace'' + '':'' as a separator + ''name'' | eclipse.ditto:thing-42 | -| `{%raw%}{{ thing:namespace }}{%endraw%}` | Namespace (i.e. first part of an ID) | eclipse.ditto | -| `{%raw%}{{ thing:name }}{%endraw%}` | Name (i.e. second part of an ID ) | thing-42 | +| `{%raw%}{{ thing:id }}{%endraw%}` | Full ID (namespace + name) | eclipse.ditto:thing-42 | +| `{%raw%}{{ thing:namespace }}{%endraw%}` | Namespace (first part of ID) | eclipse.ditto | +| `{%raw%}{{ thing:name }}{%endraw%}` | Name (second part of ID) | thing-42 | + +**Example:** Device `sensor:temperature1` provides its identity in a `device_id` header. To enforce +that it can only write to its own twin: -Assuming a device `sensor:temperature1` pushes its telemetry data to Ditto which is stored in a thing named -`sensor:temperature1`. The device identity is provided in a header field `device_id`. To enforce that the device can -only send data to the Thing `sensor:temperature1` the following enforcement configuration can be used: ```json { - "addresses": [ "telemetry/hono_tenant" ], + "addresses": ["telemetry/hono_tenant"], "authorizationContext": ["ditto:inbound-auth-subject"], "enforcement": { "input": "{%raw%}{{ header:device_id }}{%endraw%}", - "filters": [ "{%raw%}{{ thing:id }}{%endraw%}" ] + "filters": ["{%raw%}{{ thing:id }}{%endraw%}"] } } ``` -Note: This example assumes that there is a valid user named `ditto:inbound-auth-subject` in Ditto. -If you want to use a user for the basic auth (from the [HTTP API](connectivity-protocol-bindings-http.html)) use -the prefix `nginx:`, e.g. `nginx:ditto`. -See [Basic Authentication](basic-auth.html#authorization-context-in-devops-commands) for more information. - -#### Source acknowledgement requests +{% include note.html content="If you use basic auth from the HTTP API, prefix authorization subjects with `nginx:` (e.g., `nginx:ditto`). See [Basic Authentication](basic-auth.html#authorization-context-in-devops-commands)." %} -A source can configure, that for each incoming message additional -[acknowledgement requests](basic-acknowledgements.html#requesting-acks) are added. +### Source acknowledgement requests -That is desirable whenever incoming messages should be processed with a higher "quality of service" than the default, -which is "at most once" (or QoS 0). +To process inbound messages with "at least once" (QoS 1) semantics instead of the default "at most +once" (QoS 0), configure `acknowledgementRequests/includes` to request the +["twin-persisted"](basic-acknowledgements.html#built-in-acknowledgement-labels) acknowledgement. +The message is then technically acknowledged only after the twin is successfully persisted. -In order to process messages from sources with an "at least once" (or QoS 1) semantic, configure the source's -`"acknowledgementRequests/includes"` to add the -["twin-persisted"](basic-acknowledgements.html#built-in-acknowledgement-labels) acknowledgement request, which will -cause that a consumed message over this source will technically be acknowledged, it the twin was -successfully updated/persisted by Ditto. +The optional `filter` field uses an [fn:filter()](basic-placeholders.html#function-library) expression +to control when acknowledgements are requested: -How the technical acknowledgment is done is specific for the used [connection type](#connection-types) and documented -in scope of that connection type. - -In addition to the `"includes"` defining which acknowledgements to request for each incoming message, the optional -`"filter"` holds an [fn:filter()](basic-placeholders.html#function-library) function defining when to request -acknowledgements at all for an incoming message. This filter is applied on both acknowledgements: those -[requested in the message](basic-acknowledgements.html#requesting-acks-via-ditto-protocol-message) and the ones requested -via the configured `"includes"` array. - -The JSON for a source with acknowledgement requests could look like this. The `"filter"` in the example causes that -acknowledgements are only requested if the "qos" header was either not present or does not equal `0`: ```json { - "addresses": [ - "" - ], + "addresses": [""], "authorizationContext": ["ditto:inbound-auth-subject"], "headerMapping": { "qos": "{%raw%}{{ header:qos }}{%endraw%}" }, "acknowledgementRequests": { - "includes": [ - "twin-persisted", - "receiver-connection-id:my-custom-ack" - ], + "includes": ["twin-persisted", "{%raw%}{{connection:id}}{%endraw%}:my-custom-ack"], "filter": "fn:filter(header:qos,'ne','0')" } } ``` -#### Source declared acknowledgement labels +### Source declared acknowledgement labels + +Acknowledgements sent via a source must have their labels declared in the `declaredAcks` array. +Labels must be prefixed by the connection ID (or `{%raw%}{{connection:id}}{%endraw%}`) followed by a colon: -The acknowledgements sent via a source must have their labels declared in the field `declardAcks` as a JSON array.
    -If the label of an acknowledgement is not in the `declaredAcks` array, then the acknowledgement is rejected with -an error. The declared labels must be prefixed by the connection ID followed by a colon or the -`{%raw%}{{connection:id}}{%endraw%}` placeholder followed by a colon. For example: ```json { - "addresses": [ - "" - ], + "addresses": [""], "authorizationContext": ["ditto:inbound-auth-subject"], "declaredAcks": [ "{%raw%}{{connection:id}}{%endraw%}:my-custom-ack" @@ -189,19 +148,14 @@ an error. The declared labels must be prefixed by the connection ID followed by } ``` -#### Source header mapping +### Source header mapping -For incoming messages, an optional [header mapping](connectivity-header-mapping.html) may be applied. -Mapped headers are added to the headers of the Ditto protocol message obtained by payload mapping. -The default [Ditto payload mapper](connectivity-mapping.html#ditto-mapper) does not retain any external header; -in this case all Ditto protocol headers come from the header mapping. +You can apply an optional [header mapping](connectivity-header-mapping.html) to inbound messages. +Mapped headers are added to the Ditto protocol message produced by payload mapping: -The JSON for a source with header mapping could look like this: ```json { - "addresses": [ - "" - ], + "addresses": [""], "authorizationContext": ["ditto:inbound-auth-subject"], "headerMapping": { "correlation-id": "{%raw%}{{ header:message-id }}{%endraw%}", @@ -210,87 +164,45 @@ The JSON for a source with header mapping could look like this: } ``` -#### Source reply target +### Source reply target + +A source may define a reply target to publish responses to incoming commands. The reply target's +address and header mapping are defined within the reply target, while its payload mapping is +inherited from the parent source. -A source may define a reply target to publish the responses of incoming commands. -For a reply target, the address and header mapping are defined in itself, whereas its payload mapping is inherited -from the parent source, because a payload mapping definition specifies the transformation for both: incoming and outgoing -messages. +To publish responses at the address from the incoming command's `reply-to` header, configure source +header mapping and reply target together. If an incoming command lacks the `reply-to` header, no +response is published: -For example, to publish responses at the target address equal to the `reply-to` header of incoming commands, -define source header mapping and reply target as follows. If an incoming command does not have the `reply-to` header, -then its response is dropped. ```json { "headerMapping": { "reply-to": "{%raw%}{{ header:reply-to }}{%endraw%}" }, - "replyTarget": { - "enabled": true, - "address": "{%raw%}{{ header:reply-to }}{%endraw%}" - } -} -``` - -The reply target may contain its own header mapping (`"headerMapping"`) in order to map response headers. - -In addition, the reply target contains the expected response types (`"expectedResponseTypes"`) which should be -published to the reply target.
    -The following reply targets are available to choose from: -* **response**: Send back successful responses (e.g. responses after a Thing was successfully modified, - but also responses for [query commands](basic-signals-command.html#query-commands)). - Includes positive [acknowledgements](protocol-specification-acks.html#acknowledgements-aggregating). -* **error**: Send back error responses (e.g. thing not modifiable due to lacking permissions) -* **nack**: If negative [acknowledgement](protocol-specification-acks.html#acknowledgements-aggregating) responses should be delivered. - -This is an example `"replyTarget"` containing both header mapping and expected response types: -```json -{ "replyTarget": { "enabled": true, "address": "{%raw%}{{ header:reply-to }}{%endraw%}", "headerMapping": { "correlation-id": "{%raw%}{{ header:correlation-id }}{%endraw%}" }, - "expectedResponseTypes": [ - "response", - "error", - "nack" - ] + "expectedResponseTypes": ["response", "error", "nack"] } } ``` +The `expectedResponseTypes` control which responses are published: -Additionally, -sources can be configured with special response diversion headers to redirect responses to other connections. - -#### Response diversion configuration - -Sources can configure [response diversion](connectivity-response-diversion.html) -to redirect responses to different connections instead of the configured reply target. -This is done through special header mapping keys: - -```json -{ - "headerMapping": { - "divert-response-to-connection": "target-connection-id", - "divert-expected-response-types": "response,error,nack" - } -} -``` +* **response** -- successful responses and positive acknowledgements +* **error** -- error responses +* **nack** -- negative acknowledgements -Where: -- `divert-response-to-connection`: Target connection ID for diversion -- `divert-expected-response-types`: Comma-separated list of response types to divert +### Response diversion -This enables advanced multi-protocol workflows and response routing scenarios. -See the [Response diversion](connectivity-response-diversion.html) documentation for detailed configuration examples -and use cases. +Sources can redirect responses to different connections instead of the configured reply target using +special header mapping keys. See [Response diversion](connectivity-response-diversion.html) for details. -## Sources examples with response diversion +**Static response diversion** -- redirect all responses to a fixed connection: -### Static response diversion ```json { "addresses": ["commands/sensor"], @@ -306,29 +218,31 @@ and use cases. } ``` -### Dynamic response diversion +Where: +- `divert-response-to-connection`: Target connection ID for diversion +- `divert-expected-response-types`: Comma-separated list of response types to divert + +**Dynamic response diversion** -- route based on message content using a JavaScript payload mapper: + ```json { "addresses": ["commands/+"], "authorizationContext": ["ditto:device-commands"], "headerMapping": { - "divert-expected-response-types": "response,error" + "divert-expected-response-types": "response,error" }, "payloadMapping": ["response-router"] } ``` -With a corresponding JavaScript mapper that sets the target connection dynamically based on message content or headers. ```javascript function mapToDittoProtocolMsg(headers, textPayload, bytePayload, contentType) { - // ... mapping logic ... - - let dittoHeaders = { + var parsedPayload = JSON.parse(textPayload); + var dittoHeaders = { "correlation-id": headers["correlation-id"], "divert-response-to": determineTargetConnection(headers, parsedPayload), "divert-expected-response-types": "response,error" }; - return Ditto.buildDittoProtocolMsg( namespace, name, group, channel, criterion, action, path, dittoHeaders, value @@ -346,48 +260,41 @@ function determineTargetConnection(headers, payload) { } ``` +## Targets -### Targets - -Targets are used to connect to messages brokers / external systems in order to publish messages **to them**. +Targets publish messages **to** external systems. Outbound messages can be: -Target messages can be of the following type: -* [Thing messages](basic-messages.html) * [Thing events](basic-signals-event.html) -* [Thing live commands/responses/events](protocol-twinlive.html) +* [Thing messages](basic-messages.html) +* [Live commands/responses/events](protocol-twinlive.html) * [Policy announcements](protocol-specification-policies-announcement.html) * [Connection announcements](protocol-specification-connections-announcement.html) -Targets contain: -* one address (that is interpreted differently depending on the [connection type](#connection-types), e.g. as queue, topic, etc.), -* [topics](#target-topics-and-filtering) that will be sent to the target, -* an authorization context (see [authorization](#authorization)) specifying which - [authorization subject](basic-policy.html#subjects) is used to authorize messages to the target, and -* [header mapping](connectivity-header-mapping.html) to compute external headers from Ditto protocol headers. - +A target contains: -#### Target topics and filtering +* **address** -- interpreted as a queue, topic, etc. depending on the [connection type](#supported-connection-types) +* **topics** -- which [message types](#target-topics-and-filtering) to publish +* **authorizationContext** -- [authorization subjects](basic-policy.html#subjects) that must have READ permission +* **headerMapping** -- maps Ditto protocol headers to external headers -Which types of messages should be published to the target address, can be defined via configuration. +### Target topics and filtering -In order to only consume specific events like described in [change notifications](basic-changenotifications.html), the -following parameters can additionally be provided when specifying the `topics` of a target: +You define which message types to publish via the `topics` array. You can filter by +[namespaces](basic-changenotifications.html#filter-by-namespace) and +[RQL expressions](basic-changenotifications.html#filter-by-rql-expression): -| Description | Topic | [Filter by namespaces](basic-changenotifications.html#by-namespaces) | [Filter by RQL expression](basic-changenotifications.html#by-rql-expression) | -|-------------|-----------------|------------------|-----------| -| Subscribe for [Thing events/change notifications](basic-changenotifications.html) | `_/_/things/twin/events` | ✔ | ✔ | -| Subscribe for [Thing messages](basic-messages.html) | `_/_/things/live/messages` | ✔ | ✔ | -| Subscribe for [Thing live commands](protocol-twinlive.html) | `_/_/things/live/commands` | ✔ | ❌ | -| Subscribe for [Thing live events](protocol-twinlive.html) | `_/_/things/live/events` | ✔ | ✔ | -| Subscribe for [Policy announcements](protocol-specification-policies-announcement.html) | `_/_/policies/announcements` | ✔ | ❌ | -| Subscribe for [Connection announcements](protocol-specification-connections-announcement.html) | `_/_/connections/announcements` | ❌ | ❌ | +| Topic | Namespace filter | RQL filter | +|-------|:---:|:---:| +| `_/_/things/twin/events` | ✔ | ✔ | +| `_/_/things/live/messages` | ✔ | ✔ | +| `_/_/things/live/commands` | ✔ | ❌ | +| `_/_/things/live/events` | ✔ | ✔ | +| `_/_/policies/announcements` | ✔ | ❌ | +| `_/_/connections/announcements` | ❌ | ❌ | -The parameters are specified similar to HTTP query parameters, the first one separated with a `?` and all following ones -with `&`. You need to URL-encode the filter values before using them in a configuration. +Filter parameters use HTTP query parameter syntax (`?` for the first, `&` for subsequent). URL-encode +filter values before using them: -For example, this way the connection session would register for all events in the namespace `org.eclipse.ditto` and which -would match an attribute "counter" to be greater than 42. Additionally, it would subscribe to messages in the namespace -`org.eclipse.ditto`: ```json { "address": "", @@ -396,25 +303,28 @@ would match an attribute "counter" to be greater than 42. Additionally, it would "_/_/things/twin/events?extraFields=attributes/placement&filter=gt(attributes/placement,'Kitchen')", "_/_/things/live/messages?namespaces=org.eclipse.ditto" ], - "authorizationContext": ["ditto:outbound-auth-subject", "..."] + "authorizationContext": ["ditto:outbound-auth-subject"] } ``` -#### Target topics and enrichment +### Target topics and enrichment -When extra fields should be added to outgoing messages on a connection, an `extraFields` parameter can be added -to the topic. This is supported for all topics: +You can add extra fields to outgoing messages with the `extraFields` parameter. +See [signal enrichment](basic-enrichment.html) for details. -| Description | Topic | [Enrich by extra fields](basic-enrichment.html) | -|-------------|-----------------|------------------| -| Subscribe for [Thing events/change notifications](basic-changenotifications.html) | `_/_/things/twin/events` | ✔ | -| Subscribe for [Thing messages](basic-messages.html) | `_/_/things/live/messages` | ✔ | -| Subscribe for [Thing live commands](protocol-twinlive.html) | `_/_/things/live/commands` | ✔ | -| Subscribe for [Thing live events](protocol-twinlive.html) | `_/_/things/live/events` | ✔ | -| Subscribe for [Policy announcements](protocol-specification-policies-announcement.html) | `_/_/policies/announcements` | ❌ | -| Subscribe for [Connection announcements](protocol-specification-connections-announcement.html) | `_/_/connections/announcements` | ❌ | +Not all topics support enrichment: + +| Topic | Extra fields | +|-------|:---:| +| `_/_/things/twin/events` | ✔ | +| `_/_/things/live/messages` | ✔ | +| `_/_/things/live/commands` | ✔ | +| `_/_/things/live/events` | ✔ | +| `_/_/policies/announcements` | ❌ | +| `_/_/connections/announcements` | ❌ | Example: + ```json { "address": "", @@ -422,55 +332,34 @@ Example: "_/_/things/twin/events?extraFields=attributes/placement", "_/_/things/live/messages?extraFields=features/ConnectionStatus" ], - "authorizationContext": ["ditto:outbound-auth-subject", "..."] + "authorizationContext": ["ditto:outbound-auth-subject"] } ``` -#### Target issued acknowledgement label +### Target issued acknowledgement label -A target can be configured to automatically [issue acknowledgements](basic-acknowledgements.html#issuing-acknowledgements) -for each published/emitted message, once the underlying channel confirmed -that the message was successfully received. +A target can automatically [issue an acknowledgement](basic-acknowledgements.html#issuing-acknowledgements) +once the channel confirms successful delivery. The label must be prefixed by the connection ID +or `{%raw%}{{connection:id}}{%endraw%}`: -That is desirable whenever outgoing messages (e.g. [events](basic-signals-event.html)) are handled in scope of a command -sent with an "at least once" (QoS 1) semantic in order to only acknowledge that command, if the event was successfully -forwarded into another system. - -For more details on that topic, please refer to the [acknowledgements](basic-acknowledgements.html) section. - -Whether an outgoing message is treated as successfully sent or not is specific for the used -[connection type](#connection-types) and documented in scope of that connection type. - -The issued acknowledgement label must be prefixed by the connection ID followed by a colon or the -`{%raw%}{{connection:id}}{%endraw%}` placeholder followed by a colon.
    -The JSON for a target with issued acknowledgement labels could look like this: ```json { "address": "", - "topics": [ - "_/_/things/twin/events" - ], - "authorizationContext": ["ditto:inbound-auth-subject"], + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], "issuedAcknowledgementLabel": "{%raw%}{{connection:id}}{%endraw%}:my-custom-ack" } ``` -#### Target header mapping +### Target header mapping -For outgoing messages, an optional [header mapping](connectivity-header-mapping.html) may be applied. -Mapped headers are added to the external headers. -The default [Ditto payload mapper](connectivity-mapping.html#ditto-mapper) does not define any external header; -in this case, all external headers come from the header mapping. +You can apply an optional [header mapping](connectivity-header-mapping.html) to outgoing messages: -The JSON for a target with header mapping could like this: ```json { "address": "", - "topics": [ - "_/_/things/twin/events", - "_/_/things/live/messages?namespaces=org.eclipse.ditto" - ], - "authorizationContext": ["ditto:inbound-auth-subject"], + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], "headerMapping": { "message-id": "{%raw%}{{ header:correlation-id }}{%endraw%}", "content-type": "{%raw%}{{ header:content-type }}{%endraw%}", @@ -480,107 +369,83 @@ The JSON for a target with header mapping could like this: } ``` +## Authorization -### Authorization - -A connection is initiated by the connectivity service. This obviates the need for client authorization, because -Ditto becomes the client in this case. Nevertheless, to access resources within Ditto, the connection must know on -whose behalf it is acting. This is controlled via the configured `authorizationContext`, which holds a list of -self-assigned authorization subjects. Before a connection can access a Ditto resource, one of its -`authorizationSubject`s must be granted the access rights by the authorization mechanism of a +Ditto initiates connections as a client, so no client authorization is needed from the external +system. However, to access Ditto resources, each connection must specify an `authorizationContext` +with self-assigned authorization subjects. These subjects must be granted access through [Policies](basic-policy.html). -A connection target can only send data for things to which it has READ rights, as data flows from a thing to a target. -A connection source can only receive data for things to which it has WRITE rights, as data flows from a source to a thing. - -### Specific configuration - -Some [connection types](#connection-types) require specific configuration, which is not supported for other connection types. -Those are put into the `specificConfig` field. - -### Payload Mapping - -For more information on mapping message payloads see the corresponding [Payload Mapping Documentation](connectivity-mapping.html). +* A **target** can only send data for things to which it has **READ** rights +* A **source** can only receive data for things to which it has **WRITE** rights ## Placeholders -The configuration of a connection allows to use placeholders at certain places. This allows more fine-grained control -over how messages are consumed or where they are published to. The general syntax of a placeholder is -`{% raw %}{{ placeholder }}{% endraw %}`. Have a look at the [placeholders concept](basic-placeholders.html) for -more details on that. +Connection configurations support placeholders with the syntax +`{% raw %}{{ placeholder }}{% endraw %}`. See the [placeholders concept](basic-placeholders.html) +for full details. ### Placeholder for source authorization subjects -Processing the messages received via a source using the _same fixed authorization subject_ may not be -suitable for every scenario. For example, if you want to declare fine-grained write permissions per device, this would -not be possible with a fixed global subject. For this use case, we have introduced placeholder substitution for -authorization subjects of source addresses that are resolved when processing messages from a source. -Of course, this requires the sender of the -message to provide necessary information about the original issuer of the message. +You can use header placeholders in source authorization subjects to apply per-device permissions: - {% - include important.html content="Only use this kind of placeholder if you trust the source of the message. The value from the header is used as the **authorized subject**." additionalStyle="" - %} - -You can access any header value of the incoming message by using a placeholder like `{% raw %}{{ header:name }}{% endraw %}`. +{% + include important.html content="Only use this kind of placeholder if you trust the source of the message. The value from the header is used as the **authorized subject**." additionalStyle="" +%} -Example: - -Assuming the messages received from the source _telemetry_ contain a `device_id` header (e.g. _sensor-123_), -you may configure your source's authorization subject as follows: ```json - { - "id": "auth-subject-placeholder-example", - "sources": [ - { - "addresses": [ "telemetry" ], - "authorizationContext": ["device:{% raw %}{{ header:device_id }}{% endraw %}"] - } - ] - } +{ + "id": "auth-subject-placeholder-example", + "sources": [{ + "addresses": ["telemetry"], + "authorizationContext": ["device:{% raw %}{{ header:device_id }}{% endraw %}"] + }] +} ``` -The placeholder is then replaced by the value from the message headers and the message is forwarded and processed under -the subject _device:sensor-123_. -In case the header cannot be resolved or the header contains unexpected characters, an exception is thrown, which is sent -back to the sender as an error message, if a valid _reply-to_ header was provided, otherwise the message is dropped. ### Placeholder for target addresses -Another use case for placeholders may be to publish twin events or live commands and events to a target address -containing thing-specific information e.g. you can distribute things from different namespaces to different target addresses. -You can use the placeholders `{% raw %}{{ thing:id }}{% endraw %}`, `{% raw %}{{ thing:namespace }}{% endraw %}` -and `{% raw %}{{ thing:name }}{% endraw %}` in the target address for this purpose. -For a thing with the ID _org.eclipse.ditto:device-123_ these placeholders would be resolved as follows: +You can use thing placeholders in target addresses to route messages by namespace or device: | Placeholder | Description | Resolved value | |--------|------------|------------| -| `thing:id` | Full ID composed of _namespace_ `:` (as a separator), and _name_ | _org.eclipse.ditto:device-123_ | -| `thing:namespace` | Namespace (i.e. first part of an ID) | _org.eclipse.ditto_ | -| `thing:name` | Name (i.e. second part of an ID ) | _device-123_ | +| `thing:id` | Full ID (namespace:name) | `org.eclipse.ditto:device-123` | +| `thing:namespace` | Namespace | `org.eclipse.ditto` | +| `thing:name` | Name | `device-123` | -Additionally to the placeholders mentioned above, all documented -[connection placeholders](basic-placeholders.html#scope-connections) may be -used in target addresses. However, if any placeholder in the target address fails to resolve, then the message will be -dropped. +All [connection placeholders](basic-placeholders.html#scope-connections) are also available. +If any placeholder fails to resolve, the message is dropped. -Example: - -Sending live commands and events to a target address that contains the thing's namespace. ```json - { - "id": "target-placeholder-example", - "targets": [ - { - "addresses": [ "live/{% raw %}{{ thing:namespace }}{% endraw %}" ], - "authorizationContext": ["ditto:auth-subject"], - "topics": [ "_/_/things/live/events", "_/_/things/live/commands" ] - } - ] - } +{ + "id": "target-placeholder-example", + "targets": [{ + "addresses": ["live/{% raw %}{{ thing:namespace }}{% endraw %}"], + "authorizationContext": ["ditto:auth-subject"], + "topics": ["_/_/things/live/events", "_/_/things/live/commands"] + }] +} ``` +## Specific configuration + +Some connection types require protocol-specific settings in the `specificConfig` field. See each +protocol binding page for details. + +## Payload mapping + +You can transform message payloads between external formats and Ditto Protocol using +[payload mapping](connectivity-mapping.html). + ## SSH tunneling -Ditto supports tunneling a connection by establishing an SSH tunnel and using it to connect to the actual endpoint. +Ditto supports tunneling connections through SSH. See [SSH tunneling](connectivity-ssh-tunneling.html) +for setup instructions. + +## Further reading -See [SSH tunneling](connectivity-ssh-tunneling.html) on how to setup and configure SSH tunneling with Ditto. +* [Managing connections](connectivity-manage-connections.html) -- create, modify, and monitor connections +* [Payload mapping](connectivity-mapping.html) -- transform message payloads +* [Header mapping](connectivity-header-mapping.html) -- map external headers +* [TLS certificates](connectivity-tls-certificates.html) -- secure connections with TLS +* [Acknowledgements](basic-acknowledgements.html) -- configure delivery guarantees diff --git a/documentation/src/main/resources/pages/ditto/basic-enrichment.md b/documentation/src/main/resources/pages/ditto/basic-enrichment.md index 62ebe144ecf..9d050adb2ca 100644 --- a/documentation/src/main/resources/pages/ditto/basic-enrichment.md +++ b/documentation/src/main/resources/pages/ditto/basic-enrichment.md @@ -1,38 +1,49 @@ --- -title: Signal enrichment +title: Signal Enrichment keywords: change, event, enrich, extra, enrichment, fields, extraFields tags: [protocol] permalink: basic-enrichment.html --- -[Signals](basic-signals.html) which are emitted to subscribers via [WebSocket API](httpapi-protocol-bindings-websocket.html), -[HTTP SSEs](httpapi-sse.html) or established [connections](basic-connections.html) may be enriched -by `extraFields` to also be included in the sent message. +Signal enrichment lets you attach additional Thing data to events and messages when they are +delivered to subscribers, so you get the context you need without making extra API calls. -[Events](basic-signals-event.html), for example, only contain the actually changed data by default, so when they are -subscribed to via one of the APIs listed above, the data they contain may be as sparse as: -"temperature value was changed to 23.4 for the thing with ID xx". +{% include callout.html content="**TL;DR**: Use `extraFields` when subscribing to events or messages to include +additional Thing data (like attributes or related properties) in each delivered signal." type="primary" %} -Often it is helpful to additionally include some extra fields as context to be included when subscribing -(e.g. via WebSocket or a connection). For example in order to include static metadata stored in the `attributes`. +## Why use enrichment? -Therefore, it is possible to define `extraFields` to include when subscribing for: -* [events/change notifications](basic-changenotifications.html) -* [messages](basic-messages.html) -* [live commands](protocol-twinlive.html) -* [live events](protocol-twinlive.html) +[Events](basic-signals-event.html) contain only the data that changed. A temperature update event +might tell you: "the temperature of Thing X changed to 23.4". But your application might also +need to know *where* that sensor is located or *what unit* the reading uses. + +Without enrichment, you would need a separate API call to look up that context. With enrichment, +you define `extraFields` once when you subscribe, and Ditto includes those fields automatically +in every delivered signal. + +You can enrich: + +* [Events / change notifications](basic-changenotifications.html) +* [Messages](basic-messages.html) +* [Live commands](protocol-twinlive.html) +* [Live events](protocol-twinlive.html) + +## How to specify extraFields + +The `extraFields` parameter uses the same syntax as [field selectors](httpapi-concepts.html#field-selectors) +for partial Thing retrieval. How you pass it depends on the API: -How the `extraFields` are specified is depending on the API, please find the specific API information here: * [WebSocket enrichment](httpapi-protocol-bindings-websocket.html#enrichment) -* [SSE field enrichment](httpapi-sse.html#field-enrichment) +* [SSE field enrichment](httpapi-sse.html#by-field-enrichment) * [Connection target enrichment](basic-connections.html#target-topics-and-enrichment) -The `extra` data is added to the [extra field in Ditto Protocol messages](protocol-specification.html#extra) being an -JSON object containing all selected fields. +The enriched data appears in the [extra field](protocol-specification.html#extra) of the Ditto +Protocol message as a JSON object. ## Example -For example a Thing could look like this: +Consider a Thing with this structure: + ```json { "thingId": "org.eclipse.ditto:fancy-thing", @@ -44,7 +55,7 @@ For example a Thing could look like this: "temperature": { "properties": { "value": 23.42, - "unit": "Celcius" + "unit": "Celsius" } }, "humidity": { @@ -57,8 +68,8 @@ For example a Thing could look like this: } ``` -Now whenever its temperature is modified you normally only get the following information in the event -(this is a [Ditto Protocol](protocol-specification.html) message): +Without enrichment, a temperature change event contains only the changed value: + ```json { "topic": "org.eclipse.ditto/fancy-thing/things/twin/events/modified", @@ -69,17 +80,16 @@ Now whenever its temperature is modified you normally only get the following inf } ``` -What you could want is to: -* additionally add the `attributes` -* additionally add the `unit` value of the temperature +### Adding extra context -In that case you would define to include `extraFields` -(syntax is the same as for retrieving partial things with [field selector](httpapi-concepts.html#with-field-selector)): -``` +To include the Thing's location and the temperature unit, subscribe with: + +```text extraFields=attributes,features/temperature/properties/unit ``` -In that case, each emitted Ditto Protocol event would include an `extra` section containing the selected data: +Each event now includes an `extra` section with the requested data: + ```json { "topic": "org.eclipse.ditto/fancy-thing/things/twin/events/modified", @@ -89,12 +99,12 @@ In that case, each emitted Ditto Protocol event would include an `extra` section "revision": 34, "extra": { "attributes": { - "location": "kitchen" + "location": "Kitchen" }, "features": { "temperature": { "properties": { - "unit": "Celcius" + "unit": "Celsius" } } } @@ -102,28 +112,45 @@ In that case, each emitted Ditto Protocol event would include an `extra` section } ``` -It is possible to use the wildcard operator '*' as feature ID and add a property of multiple features -(syntax is the same as for [field selector with wildcard](httpapi-concepts.html#field-selector-with-wildcard)). -This would add the property 'unit' of all features: -``` +### Using wildcards + +Use the wildcard `*` as a feature ID to include a property from all features: + +```text extraFields=features/*/properties/unit ``` -If you however want to see a property only for the features changed within this event you could make use of placeholders. -The following example would enrich the unit of all features that have changed within this event: -``` +### Using placeholders + +Use placeholders to enrich only with data from the feature that triggered the event: + +```text {%raw%}extraFields=features/{{feature:id}}/properties/unit{%endraw%} ``` -{% include note.html content="Please note that 'deleted' events cannot be enriched with the deleted values." %} +{% include note.html content="Deleted events cannot be enriched with the deleted values." %} -Please have a look at available placeholders for the use case: -* [Signal enrichment for Websocket](basic-placeholders.html#scope-websocket-signal-enrichment) +For the full list of available placeholders, see: +* [Signal enrichment for WebSocket](basic-placeholders.html#scope-websocket-signal-enrichment) * [Signal enrichment for SSE](basic-placeholders.html#scope-sse-signal-enrichment) * [Signal enrichment for connections](basic-placeholders.html#scope-connections) -## Enrich and filter +## Enrichment with filtering + +You can combine `extraFields` with [event filtering](basic-changenotifications.html#filtering). +Enriched data becomes available to the filter expression, so you can filter based on values that +are not part of the change itself. + +For example, only receive temperature events for Things in the kitchen: + +```text +extraFields=attributes/location&filter=eq(attributes/location,"Kitchen") +``` + +## Further reading -In combination with [event filtering](basic-changenotifications.html#filtering) enriched data can also be used to -filter. For example, when selecting `extraFields=attributes/location`, an additional `filter` may define to only -emit events for a certain location: `extraFields=attributes/location&filter=eq(attributes/location,"kitchen")`. +* [Change Notifications](basic-changenotifications.html) -- subscribe to events +* [HTTP API concepts: field selectors](httpapi-concepts.html#field-selectors) -- syntax for + selecting fields +* [Ditto Protocol extra field](protocol-specification.html#extra) -- where enriched data appears + in the protocol message diff --git a/documentation/src/main/resources/pages/ditto/basic-errors.md b/documentation/src/main/resources/pages/ditto/basic-errors.md index ce86ac8315a..67928af880e 100644 --- a/documentation/src/main/resources/pages/ditto/basic-errors.md +++ b/documentation/src/main/resources/pages/ditto/basic-errors.md @@ -5,57 +5,60 @@ tags: [model] permalink: basic-errors.html --- -Errors are datatypes containing information about occurred failures which were either -cause by the user or appeared in the server. +Errors are structured responses that describe failures caused by client mistakes or server problems. -## Error model specification +{% include callout.html content="**TL;DR**: Every error includes an HTTP `status` code, an `error` code string, a human-readable `message`, and an optional `description` with resolution hints. Use the `status` code for programmatic handling -- the `error` code string may change without notice." type="primary" %} + +## Overview + +When a Ditto operation fails, Ditto returns an error response containing details about what went wrong and how you might fix it. + +## Error model {% include docson.html schema="jsonschema/error.json" %} ### Status -The "status" uses HTTP status codes semantics (see [RFC 7231](https://tools.ietf.org/html/rfc7231#section-6)) -to indicate whether a specific command has been successfully completed, or not. +The `status` field uses HTTP status code semantics (see [RFC 7231](https://tools.ietf.org/html/rfc7231#section-6)) to indicate whether a command succeeded or failed. -These "status" codes can be seen as API/contract which will be always the same for a specific error. -Use the "status" in order to identify an error, as the additional "error" and "description" might change -without prior notice. +The `status` code is part of Ditto's stable API. Use it to identify and handle errors programmatically. -### Error +### Error code -A Ditto error contains an "error" code which is a string identifier that uniquely identifies the error. +The `error` field contains a string identifier that uniquely identifies the error type (e.g., `things:attribute.notfound`). -These error codes Ditto provides in addition to the HTTP **status** code are not to be considered as API and must -therefore not be relied on. -They might change without prior notice. +These error codes are **not** part of the stable API and may change without notice. Do not rely on them for programmatic error handling. -Ditto itself uses the following prefixes for its error codes: +Ditto uses these prefixes for its error codes: -* `things:` - for errors related to [things](basic-thing.html) -* `policies:` - for errors related to [policies](basic-policy.html) -* `things-search:` - for errors related to the [things search](basic-search.html) -* `acknowledgement:` - for errors related to [acknowledgements](basic-acknowledgements.html) -* `messages:` - for errors related to [messages](basic-messages.html) -* `placeholder:` - for errors related to [placeholders](basic-placeholders.html) -* `jwt:` - for errors related to JWT based [authentication](basic-auth.html) -* `gateway:` - for errors produced by the (HTTP/WS) [gateway](architecture-services-gateway.html) service -* `connectivity:` - for errors produced by the [connectivity](architecture-services-connectivity.html) service +| Prefix | Domain | +|---|---| +| `things:` | [Things](basic-thing.html) | +| `policies:` | [Policies](basic-policy.html) | +| `things-search:` | [Things search](basic-search.html) | +| `acknowledgement:` | [Acknowledgements](basic-acknowledgements.html) | +| `messages:` | [Messages](basic-messages.html) | +| `placeholder:` | [Placeholders](basic-placeholders.html) | +| `jwt:` | JWT-based [authentication](basic-auth.html) | +| `gateway:` | [Gateway](architecture-services-gateway.html) service | +| `connectivity:` | [Connectivity](architecture-services-connectivity.html) service | ### Message -The error "message" contains a short message describing the encountered problem in plain english text. +The `message` field contains a short, human-readable description of the problem. ### Description -The optional error "description" describes in more detail how the error could be resolved. +The optional `description` field provides more detail on how to resolve the error. ### Href -The optional href contains a link to Ditto documentation or external resources in order to help to resolve the error. - +The optional `href` field links to Ditto documentation or external resources that help resolve the error. ## Examples +A "not found" error: + ```json { "status": 404, @@ -65,6 +68,8 @@ The optional href contains a link to Ditto documentation or external resources i } ``` +An "invalid ID" error: + ```json { "status": 400, @@ -75,3 +80,7 @@ The optional href contains a link to Ditto documentation or external resources i } ``` +## Further reading + +- [Protocol errors](protocol-specification-errors.html) -- error format in Ditto Protocol messages +- [Protocol specification](protocol-specification.html) -- the full message format reference diff --git a/documentation/src/main/resources/pages/ditto/basic-feature.md b/documentation/src/main/resources/pages/ditto/basic-feature.md index b87beea9ed1..c32805bc116 100644 --- a/documentation/src/main/resources/pages/ditto/basic-feature.md +++ b/documentation/src/main/resources/pages/ditto/basic-feature.md @@ -1,89 +1,139 @@ --- -title: Feature +title: Features keywords: definition, properties, desiredProperties, entity, feature, functionblock, informationmodel, model tags: [model] permalink: basic-feature.html --- -A Feature is used to manage all data and functionality of a Thing that can be clustered in an outlined technical -context. +A Feature groups related state data and capabilities of a [Thing](basic-thing.html) under a named +identifier -- for example, a "temperature" feature on a weather station or a "lamp" feature on a +smart light. -For different contexts or aspects of a Thing different Features can be used which are all belonging to the same Thing -and do not exist without this Thing. +{% include callout.html content="**TL;DR**: A Feature has an ID, properties (current state), optional desired +properties (target state), and an optional definition that links it to a type model." type="primary" %} -## Model specification +## How Features work -The feature model in API version 2: +Each Feature within a Thing represents one functional aspect of the device. A smart thermostat +might have these Features: -{% include docson.html schema="jsonschema/feature_v2.json" %} +* `temperature` -- current and target temperature readings +* `humidity` -- current humidity level +* `schedule` -- heating schedule configuration + +You access each Feature independently through the API. For example, to read the current temperature: + +```bash +curl -u ditto:ditto -X GET \ + 'http://localhost:8080/api/2/things/com.example:thermostat-1/features/temperature/properties/value' +``` +## Model specification + +{% include docson.html schema="jsonschema/feature_v2.json" %} ## Feature ID -Within a Thing each Feature is identified by a unique string - the so-called Feature ID. -A Feature ID often needs to be set in the path of an HTTP request. Due to this fact we strongly recommend using a -restricted set of characters (e.g. those for [Uniform Resource Identifiers (URI)](https://www.ietf.org/rfc/rfc3986.txt)). +Every Feature within a Thing has a unique string identifier. Since Feature IDs often appear in HTTP +request paths, use characters that are safe in +[URIs](https://www.ietf.org/rfc/rfc3986.txt) -- letters, digits, hyphens, and underscores. -The Feature ID may not be the wildcard operator `*` because it has a special meaning and can lead to unexpected -results when retrieving or searching for things/features. +The Feature ID must not be the wildcard operator `*`, because it has special meaning in queries +and can produce unexpected results. ## Feature properties -The **data** related to Features is managed in form of a **list of properties**. These properties can be categorized, -e.g. to manage the status, the configuration or any fault information. -Feature properties are represented as one JSON object. - -Each property itself can be either a simple/scalar value or a complex object; allowed is any JSON value. +Properties hold the Feature's current state data as a JSON object. Each property can be a scalar +value or a nested object -- any valid JSON value works: + +```json +{ + "properties": { + "value": 23.5, + "unit": "Celsius", + "location": { + "latitude": 48.1351, + "longitude": 11.5820 + } + } +} +``` ## Feature desired properties -Desired properties represent the desired state of the properties. They are a tool to represent the desired target state -of the properties. -The **desiredProperties** related to Features are managed in form of a **list of properties**. These desired properties -can be categorized, e.g. to manage the status, the configuration or any fault information. -Feature desired properties are represented as one JSON object. +Desired properties represent the **target state** you want the device to reach. They have the same +structure as regular properties but describe what *should be* rather than what *is*. -Each desired property itself can be either a simple/scalar value or a complex object; allowed is any JSON value. +For example, you might set a desired temperature of 22.0 while the current temperature reads 19.5: -Please note however, that besides persisting the desired properties, and indexing the fields for search requests, filtering -etc. for the time being, Ditto does not implement their further processing. Such functionality will come with future releases. +```json +{ + "properties": { + "value": 19.5 + }, + "desiredProperties": { + "value": 22.0 + } +} +``` -## Feature definition +Ditto persists desired properties and indexes them for search queries. The logic for reconciling +desired state with actual state is up to your application or device firmware. -Ditto supports specifying a definition for a feature in order to document how a feature's state is structured -(in [properties](#feature-properties)), and which behavior/capabilities -([messages related to features](basic-messages.html)) can be expected from such a feature. +## Feature definition -A feature's definition is a list of definition identifiers containing -* either valid HTTP(s) URLs (e.g. in order to define that the Feature is described by [WoT (Web of Things) Thing Models](basic-wot-integration.html#thing-model-describing-a-ditto-feature)) -* or a *namespace*, *name* and *version* separated by colons: `::` - * in that case the "knowledge" where to look up the definition must be managed somewhere else +A definition links the Feature to a type model that documents the Feature's structure and +capabilities. You specify definitions as a list containing: -A definition can be seen as a type for features. The [properties](#feature-properties) of a -feature containing a definition identifier `"org.eclipse.ditto:lamp:1.0.0"` can be expected to follow the structure -described in the `lamp` type of namespace `org.eclipse.ditto` semantically versioned with version `1.0.0`. +* **HTTP(s) URLs** -- for example, a [WoT (Web of Things) Thing Model](basic-wot-integration.html#thing-model-describing-a-ditto-feature) +* **Identifier strings** in the format `::` -- for example, + `org.eclipse.ditto:lamp:1.0.0` -{% include note.html content="Ditto does not contain a type system on its own and does not specify how to describe types. - You may e.g. use the official [W3C Web of Things (WoT)](#the-link-to-w3c-wot-web-of-things) standard - to describe data structures and supported messages of Ditto features." %} +A definition acts as a type annotation. If a Feature has the definition `org.eclipse.ditto:lamp:1.0.0`, +you can expect its properties to follow the structure described by version `1.0.0` of the "lamp" type +in the `org.eclipse.ditto` namespace. -Currently, Ditto **does not** ensure that the `properties` or `desiredProperties` of a feature or its supported messages -must follow the type defined in the definition. -This can be ensured e.g. before sending a property to Ditto or before sending a message. +{% include note.html content="Ditto does not contain a built-in type system and does not validate that properties + match their definition. You can use the [W3C Web of Things (WoT)](#the-link-to-w3c-wot-web-of-things) standard + to describe data structures and supported messages for Ditto features." %} ### The link to W3C WoT (Web of Things) -If a [feature definition](#feature-definition) has the form of an HTTP(s) URL, this URL pointing to a resource may be -interpreted as the link to a [W3C WoT (Web of Things)](https://www.w3.org/TR/wot-thing-description11/) -[Thing Model](https://www.w3.org/TR/wot-thing-description11/#thing-model) in [JSON-LD](https://www.w3.org/TR/json-ld11/) -format. - -For a detailed explanation how WoT and its concepts link to Ditto, please consult the -[dedicated WoT integration documentation](basic-wot-integration.html). - - -# Example +When a [feature definition](#feature-definition) is an HTTP(s) URL, that URL can point to a +[W3C WoT Thing Model](https://www.w3.org/TR/wot-thing-description11/#thing-model) in +[JSON-LD](https://www.w3.org/TR/json-ld11/) format. + +For a detailed explanation of how WoT concepts map to Ditto, see the +[WoT integration documentation](basic-wot-integration.html). + +## Example + +A Thing with two Features -- one for a heating element, one for a switch: + +```json +{ + "heating-no1": { + "properties": { + "connected": true, + "temperature": 21.5 + }, + "desiredProperties": { + "temperature": 23.0 + } + }, + "switchable": { + "definition": [ "org.eclipse.ditto:Switcher:1.0.0" ], + "properties": { + "on": true, + "lastToggled": "2017-11-15T18:21Z" + } + } +} +``` + +## Further reading -Please inspect the following separate pages for the WoT integration and an example: * [WoT (Web of Things) integration](basic-wot-integration.html) * [WoT (Web of Things) example](basic-wot-integration-example.html) +* [Things](basic-thing.html) -- the parent entity that contains Features +* [Messages](basic-messages.html) -- send commands to specific Features diff --git a/documentation/src/main/resources/pages/ditto/basic-history.md b/documentation/src/main/resources/pages/ditto/basic-history.md index 8b6a019f7ce..3c0b8e01a20 100644 --- a/documentation/src/main/resources/pages/ditto/basic-history.md +++ b/documentation/src/main/resources/pages/ditto/basic-history.md @@ -5,171 +5,137 @@ tags: [history] permalink: basic-history.html --- -Starting with **Eclipse Ditto 3.2.0**, APIs for retrieving the history of the following entities is provided: -* [things](basic-thing.html) -* [policies](basic-policy.html) -* [connections](basic-connections.html) +Ditto provides APIs for retrieving the history of Things, Policies, and Connections, letting you inspect past states and stream modification events. -The capabilities of these APIs are the following: +{% include callout.html content="**TL;DR**: You can retrieve any entity at a specific revision or timestamp, and stream historical modification events for Things and Policies. Configure `history-retention-duration` to control how long historical data is kept." type="primary" %} -| Entity | [Retrieving entity at a specific revision or timestamp](#retrieving-entity-from-history) | [Streaming modification events of an entity specifying from/to revision/timestamp](#streaming-historical-events-of-entity) | -|------------|------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| -| Thing | ✓ | ✓ | -| Policy | ✓ | ✓ | -| Connection | ✓ | no | +## Overview + +Since Eclipse Ditto 3.2.0, you can access the history of: + +* [Things](basic-thing.html) +* [Policies](basic-policy.html) +* [Connections](basic-connections.html) + +| Entity | Retrieve at revision/timestamp | Stream historical events | +|---|---|---| +| Thing | Yes | Yes | +| Policy | Yes | Yes | +| Connection | Yes | No | {% include note.html content="Ditto's history API capabilities are not comparable with the features of a time series database. E.g. no aggregations on or compactions of the historical data can be done." %} +## Retrieving an entity from history -## Retrieving entity from history - -Provides: -* Finding out the state of an entity (thing, policy, connection) at a given: - * revision number - * timestamp -* Retrieving "historical headers" persisted together with a modification (see [configuring historical headers to persist](#configuring-historical-headers-to-persist)) +You can retrieve the state of an entity (Thing, Policy, Connection) at a given revision number or timestamp. You can also retrieve "historical headers" persisted alongside a modification (see [configuring historical headers](#configuring-historical-headers-to-persist)). -Target use cases: -* Compare changes to entity (e.g. a connection) to a former state - * In order to solve potential errors in e.g. policy or connection configuration -* **Audit log**: Find out who (which subject) did a change to an entity - * E.g. in order to find out who changed a policy/connection - * Configure [which headers to persist as historical headers](#configuring-historical-headers-to-persist) to e.g. include the subject which did a modification +Use cases: +* Compare an entity's current state to a former state for debugging +* **Audit log**: Find out which subject made a specific change -### Retrieve entity at specific revision +### Retrieve at a specific revision -Retrieving an entity at an (historical) revision, set the header `at-historical-revision` to a `long` number for all -"retrieve" commands of persisted state. +Set the `at-historical-revision` header to a revision number on any retrieve command. -Example for the HTTP API: ```bash -# Access a thing: +# Retrieve a Thing at revision 1: curl -u ditto:ditto 'http://localhost:8080/api/2/things/org.eclipse.ditto:thing-1' \ --header 'at-historical-revision: 1' - -# Access a policy: + +# Retrieve a Policy at revision 1: curl -u ditto:ditto 'http://localhost:8080/api/2/policies/org.eclipse.ditto:policy-1' \ --header 'at-historical-revision: 1' - -# Access a connection: + +# Retrieve a Connection at revision 1: curl -u devops:foobar 'http://localhost:8080/api/2/connections/some-connection-1' \ --header 'at-historical-revision: 1' ``` -The same functionality is available via a [header of a Ditto Protocol](protocol-specification.html#headers) message. +This functionality is also available via [Ditto Protocol headers](protocol-specification.html#headers). -If [historical headers](#configuring-historical-headers-to-persist) were configured to be persisted, they can be found -in the response header named `historical-headers`. +If [historical headers](#configuring-historical-headers-to-persist) are configured, they appear in the response header `historical-headers`. -### Retrieve entity at specific timestamp +### Retrieve at a specific timestamp -Retrieving an entity at an (historical) timestamp, set the header `at-historical-timestamp` to an ISO-8601 formatted -`string` for all "retrieve" commands of persisted state. +Set the `at-historical-timestamp` header to an ISO-8601 timestamp. -Example for the HTTP API: ```bash -# Access a thing: +# Retrieve a Thing at a specific time: curl -u ditto:ditto 'http://localhost:8080/api/2/things/org.eclipse.ditto:thing-1' \ --header 'at-historical-timestamp: 2022-10-24T03:11:15Z' - -# Access a policy: + +# Retrieve a Policy at a specific time: curl -u ditto:ditto 'http://localhost:8080/api/2/policies/org.eclipse.ditto:policy-1' \ --header 'at-historical-timestamp: 2022-10-24T06:11:15Z' - -# Access a connection: + +# Retrieve a Connection at a specific time: curl -u devops:foobar 'http://localhost:8080/api/2/connections/some-connection-1' \ --header 'at-historical-timestamp: 2022-10-24T07:11Z' ``` -The same functionality is available via a [header of a Ditto Protocol](protocol-specification.html#headers) message. +## Streaming historical events -If [historical headers](#configuring-historical-headers-to-persist) were configured to be persisted, they can be found -in the response header named `historical-headers`. +You can stream a sequence of modification events for a specific Thing or Policy. Specify the range by revision numbers or timestamps. +Use cases: +* Inspect how an entity changed over time +* Display historical values on a chart -## Streaming historical events of entity +### Streaming via SSE -Provides: -* A stream of changes to a specific thing or policy, based on specified: - * entity ID - * start revision number (and optional stop revision number) - * start timestamp (and optional stop timestamp) -* Retrieving "historical headers" persisted together with a modification (see [configuring historical headers to persist](#configuring-historical-headers-to-persist)) +The [SSE (Server Sent Event) API](httpapi-sse.html) is the simplest way to stream historical events. It is available **for Things only**. -Target use cases: -* Inspect the changes of an entity over time - * E.g. displaying a value on a chart with that way +Use these query parameters: -### Streaming historical events via SSE +**Revision-based:** +* `from-historical-revision`: Starting revision. Use negative values for relative offsets from the current revision. +* `to-historical-revision`: Optional end revision. Use `0` for latest, or negative for relative offsets. -The easiest way to stream historical events is the [SSE (Server Sent Event) API](httpapi-sse.html). -This API is however **only available for things** (not for policies). +**Timestamp-based:** +* `from-historical-timestamp`: Starting timestamp (ISO-8601). +* `to-historical-timestamp`: Optional end timestamp. -Use the following query parameters in order to specify the start/stop revision/timestamp. +Each historical event is normalized to the Thing JSON representation. -Either use the revision based parameters: -* `from-historical-revision`: Specifies the revision number to start streaming historical modification events from. - May also be negative in order to specify to get the last `n` revisions relative to the **most recent revision** (`_revision` of the thing). -* `to-historical-revision`: Optionally specifies the revision number to stop streaming at (if omitted, it streams events until the current state of the entity). - May also be 0 or negative in order to specify to get either the latest (`0`) or the `n`th most recent revision. - -Alternatively, use the timestamp based parameters: -* `from-historical-timestamp`: specifies the timestamp to start streaming historical modification events from -* `to-historical-timestamp`: optionally specifies the timestamp to stop streaming at (if omitted, it streams events until the current state of the entity) - -The messages sent over the SSE are the same as for the [SSE (Server Sent Event) API](httpapi-sse.html), each historical -modification event is "normalized" to the Thing JSON representation. - -Examples: ```bash -# stream complete history starting from earliest available revision of a thing: +# Stream complete history from earliest available revision: curl --http2 -u ditto:ditto -H 'Accept:text/event-stream' -N \ http://localhost:8080/api/2/things/org.eclipse.ditto:thing-2?from-historical-revision=0&fields=thingId,attributes,features,_revision,_modified -# stream specific history range of a thing based on revisions: +# Stream a specific revision range: curl --http2 -u ditto:ditto -H 'Accept:text/event-stream' -N \ http://localhost:8080/api/2/things/org.eclipse.ditto:thing-2?from-historical-revision=23&to-historical-revision=42&fields=thingId,attributes,features,_revision,_modified -# stream specific history range of a thing based on timestamps: +# Stream a specific timestamp range: curl --http2 -u ditto:ditto -H 'Accept:text/event-stream' -N \ http://localhost:8080/api/2/things/org.eclipse.ditto:thing-2?from-historical-timestamp=2022-10-24T11:44:36Z&to-historical-timestamp=2022-10-24T11:44:37Z&fields=thingId,attributes,features,_revision,_modified -# stream specific history range, additionally selecting _context in "fields" which contains the historical headers: +# Include historical headers via the _context field: curl --http2 -u ditto:ditto -H 'Accept:text/event-stream' -N \ http://localhost:8080/api/2/things/org.eclipse.ditto:thing-2?from-historical-revision=0&fields=thingId,attributes,features,_revision,_modified,_context ``` -#### Filtering streamed historical events for things via SEE - -When streaming historical events for [things](basic-thing.html), an optional `filter` in form of an -[RQL](basic-rql.html) may be declared in order to only receive thing events matching the defined query. +#### Filtering streamed events via SSE -This can e.g. be useful to only stream events in which a certain feature or a certain property/attribute was included. +Add a `filter` parameter with an [RQL](basic-rql.html) expression to only receive events matching the query: -In addition to the parameters selecting from/to revision or timestamp, the following parameter can be defined: -* `filter`: specifies the [RQL](basic-rql.html) filter which events to return in the stream must match - -Examples: ```bash -# stream complete history starting from earliest available revision of a thing, but only those where a feature "bamboo" was modified: +# Only events where a "bamboo" feature was modified: curl --http2 -u ditto:ditto -H 'Accept:text/event-stream' -N \ http://localhost:8080/api/2/things/org.eclipse.ditto:thing-2?from-historical-revision=0&fields=thingId,attributes,features,_revision,_modified&filter=exists(features/bamboo) -# stream specific history range of a thing based on timestamps, filtering for temperature values of a sensor being greater than 50: +# Only events where temperature exceeded 50: curl --http2 -u ditto:ditto -H 'Accept:text/event-stream' -N \ http://localhost:8080/api/2/things/org.eclipse.ditto:thing-2?from-historical-timestamp=2022-10-24T11:44:36Z&to-historical-timestamp=2022-10-24T11:44:37Z&fields=thingId,attributes,features,_revision,_modified&filter=gt(features/temperature/properties/value,50) ``` -### Streaming historical events via Ditto Protocol +### Streaming via Ditto Protocol -Please inspect the [protocol specification of DittoProtocol messages for streaming persisted events](protocol-specification-streaming-subscription.html) -to find out how to stream historical (persisted) events via DittoProtocol. -Using the DittoProtocol, historical events can be streamed either via WebSocket or connections. +Use the [streaming subscription protocol](protocol-specification-streaming-subscription.html) to stream historical events via WebSocket or connections. -Example protocol interaction for retrieving the persisted events of a thing: +**Step 1**: Subscribe for persisted events: -**First:** Subscribe for the persisted events of a thing ```json { "topic": "org.eclipse.ditto/thing-2/things/twin/streaming/subscribeForPersistedEvents", @@ -182,42 +148,34 @@ Example protocol interaction for retrieving the persisted events of a thing: } ``` -Alternatively to `fromHistoricalRevision` and `toHistoricalRevision`, also a timestamp based range may be used: -`fromHistoricalTimestamp` and `toHistoricalTimestamp`. -The "to" can be omitted in order to receive all events up to the current revision or timestamp. +You can also use `fromHistoricalTimestamp` and `toHistoricalTimestamp`. Omit the "to" parameter to stream up to the current state. + +Ditto responds with a `created` event containing the subscription ID: -As a result, the following `created` event is received as response: ```json { "topic": "org.eclipse.ditto/thing-2/things/twin/streaming/created", "path": "/", - "headers": {}, - "value": { - "subscriptionId": "0" - } + "value": { "subscriptionId": "0" } } ``` -**Second:** Once the streaming subscription is confirmed to be created, request demand (of how many events to get streamed), -referencing the `subscriptionId`: +**Step 2**: Request demand: + ```json { "topic": "org.eclipse.ditto/thing-2/things/twin/streaming/request", "path": "/", - "headers": {}, - "value": { - "subscriptionId": "0", - "demand": 25 - } + "value": { "subscriptionId": "0", "demand": 25 } } ``` -The backend will start sending the requested persisted events as `next` messages: +Ditto sends `next` events containing the historical events: + ```json { "topic": "org.eclipse.ditto/thing-2/things/twin/streaming/next", "path": "/", - "headers": {}, "value": { "subscriptionId": "0", "item": { @@ -227,31 +185,27 @@ The backend will start sending the requested persisted events as `next` messages } ``` -It will do so either until all existing events were sent, in that case a `complete` event is sent: +It continues until all existing events are sent, at which point it sends a `complete` event: + ```json { "topic": "org.eclipse.ditto/thing-2/things/twin/streaming/complete", "path": "/", - "headers": {}, - "value": { - "subscriptionId": "0" - } + "value": { "subscriptionId": "0" } } ``` -Or it will stop after the `demand` was fulfilled, waiting for the requester to claim more demand with a new `request` -message. +If the requested `demand` is fulfilled before all events are sent, the backend pauses and waits +for the requester to claim more demand with a new `request` message. -#### Filtering streamed historical events for things via Ditto Protocol +#### Filtering via Ditto Protocol -The `filter` for streaming historical thing events may also be specified via Ditto Protocol. +Include a `filter` in the subscribe command's value: -Example protocol message for subscribing for the persisted events of a thing with a `filter`: ```json { "topic": "org.eclipse.ditto/thing-2/things/twin/streaming/subscribeForPersistedEvents", "path": "/", - "headers": {}, "value": { "fromHistoricalRevision": 1, "toHistoricalRevision": 10, @@ -260,35 +214,32 @@ Example protocol message for subscribing for the persisted events of a thing wit } ``` - ## Configuring historical headers to persist -In the configuration of the services (things, policies, connectivity) there is a section where to configure the historical -headers to persist, for example this is the section for policies: +Configure which Ditto headers to persist alongside events in the service configuration (things, policies, connectivity): ```hocon event { - # define the DittoHeaders to persist when persisting events to the journal - # those can e.g. be retrieved as additional "audit log" information when accessing a historical policy revision historical-headers-to-persist = [ - #"ditto-originator" # who (user-subject/connection-pre-auth-subject) issued the event + #"ditto-originator" #"correlation-id" ] historical-headers-to-persist = ${?POLICY_EVENT_HISTORICAL_HEADERS_TO_PERSIST} } ``` -By default, no headers are persisted as historical headers, but it could e.g. make sense to persist the `ditto-originator` -in order to provide "audit log" functionality in order to find out who (which subject) changed a policy at which time. +By default, no headers are persisted. Persisting `ditto-originator` enables audit-log functionality (tracking who made each change). ## Cleanup retention time configuration -In order to be able to access the history of entities, their journal database entries must not be cleaned up too quickly. +To access entity history, journal entries must not be cleaned up too quickly. + +By default, Ditto enables [background cleanup](operating-devops.html#managing-background-cleanup) to remove stale data from MongoDB. If you use history capabilities, either: + +* Disable cleanup entirely (which increases database storage usage) +* Configure `history-retention-duration` to keep history for a specific duration before cleanup -By default, Ditto enables the [background cleanup](installation-operating.html#managing-background-cleanup) in order to -delete "stale" (when not using the history feature) data from the MongoDB. +## Further reading -If Ditto shall be used with history capabilities, the cleanup has either -* be disabled completely (which however could lead to a lot of used database storage) -* or be configured with a `history-retention-duration` of a duration how long to keep "the history" before cleaning up - snapshots and events +- [Streaming subscription protocol](protocol-specification-streaming-subscription.html) -- reactive-streams protocol for historical events +- [SSE API](httpapi-sse.html) -- server-sent events for streaming diff --git a/documentation/src/main/resources/pages/ditto/basic-messages.md b/documentation/src/main/resources/pages/ditto/basic-messages.md index 852c4245224..b0dc00c3327 100644 --- a/documentation/src/main/resources/pages/ditto/basic-messages.md +++ b/documentation/src/main/resources/pages/ditto/basic-messages.md @@ -5,168 +5,128 @@ tags: [model] permalink: basic-messages.html --- -Messages *do not affect* the state of a digital twin or an actual device. -Therefore, Ditto does not handle messages like [commands](basic-signals-command.html): there are no responses which are -produced by Ditto and no events which are emitted for messages. +Messages let you send arbitrary payloads to or from a device through its digital twin. Unlike +[commands](basic-signals-command.html), messages do not change the twin's state -- Ditto routes +them without inspecting their content. - {% - include note.html content="Ditto has no knowledge of the payload of messages but merely routes messages between - connected devices." - %} +{% include callout.html content="**TL;DR**: Messages are fire-and-forget payloads routed through Ditto to or from +devices. Ditto does not interpret message content, does not guarantee delivery, and does not retain +messages for offline devices." type="primary" %} -Messages provide the possibility to send something **to** or **from** an actual device using an arbitrary subject/topic. -They contain a custom payload with a custom content-type, so you can choose what content best -fits your solution. +## How messages work -Expressed differently, Messages -* **to** devices are operations which should trigger an action on a device (e.g. with a subject `turnOff`), -* **from** devices are events/alarms which are emitted by devices (e.g. with a subject `smokeDetected`). +You send a message **to** a device to trigger an action (for example, `turnOff`). A device sends +a message **from** itself to report something (for example, `smokeDetected`). Ditto routes the +message to all currently connected and authorized recipients. {% include image.html file="pages/basic/ditto-messages.png" alt="Ditto Messages" caption="How Ditto acts as router for Messages" max-width=600 %} +{% include note.html content="Ditto has no knowledge of the payload of messages but routes messages between +connected devices." %} -## Characteristics of Messages - -Eclipse Ditto is not a message broker and does not want to offer features a message broker does. - -It can be seen as a message router which: -* accepts messages via 2 APIs ([HTTP](httpapi-messages.html) and - [Ditto Protocol](protocol-specification-things-messages.html), e.g. via [WebSocket binding](httpapi-protocol-bindings-websocket.html)) -* checks for **currently connected** interested parties whether they may receive a specific Message - (performs [authorization checks](basic-auth.html#authorization)) -* routes the Message and reply Messages in between connected clients - - {% include warning.html content="Ditto offers no message retention. If a device isn't connected when a Message should - be routed, it will never receive the Message." - %} - - {% include warning.html content="Ditto makes no statement about Message QoS. Messages are routed **at most once**." - %} - - {% include warning.html content="Ditto does deliver messages only in \"fan out\" style, - if the same credentials are connected twice, both connections will receive Messages if the credential is authorized - to read a Message." - %} - - -## Elements - -Ditto messages always have to have at least these elements: -* **Direction**: *to* / *from*, -* **Thing ID**: the ID of the `Thing` (actual device) which should receive/send the message and -* **Subject**: the custom subject/topic. - -Additionally, they can contain more information: -* **Feature ID**: if a message should be addressed to the [Feature](basic-feature.html) of a Thing, this specifies - its ID. -* **content-type**: defines which content-type the optional payload has. -* **correlation-id**: Ditto can route message responses back to the issuer of a message. Therefore, a correlation-id has - to be present in the message. +### Important characteristics +Ditto is a message **router**, not a message **broker**: -## Payload +{% include warning.html content="Ditto offers no message retention. If a device is not connected when a message is + routed, it will never receive the message." %} -A message can optionally contain a payload. As Ditto does neither have to understand the message nor its payload, the -content-type and serialization is arbitrary. +{% include warning.html content="Ditto makes no statement about message QoS. Messages are routed **at most once**." %} +{% include warning.html content="Ditto delivers messages in a fan-out style. If the same credentials are connected + twice, both connections receive the message (assuming authorization permits it)." %} -## APIs +## Message elements -Messages can be sent via: -* the [WebSocket API](httpapi-protocol-bindings-websocket.html) as [Ditto Protocol](protocol-overview.html) messages, -* the [HTTP API](httpapi-overview.html) either as "fire and forget" messages or, when expecting a response, in a - blocking way at the [Messages HTTP API endpoint](http-api-doc.html#/Messages) -* Ditto managed [connection sources](basic-connections.html#sources) when receiving messages in [Ditto Protocol](protocol-overview.html) - via the source +Every message requires these fields: -Messages can be received via: -* the [WebSocket API](httpapi-protocol-bindings-websocket.html) as [Ditto Protocol](protocol-overview.html) messages -* the [Server Sent Event API](httpapi-sse.html#subscribe-for-messages-for-a-specific-thing) -* Ditto managed [connection targets](basic-connections.html#target-topics-and-filtering) when "subscribing for Thing messages" +| Field | Description | +|-------|-------------| +| **Direction** | `to` (toward the device) or `from` (from the device) | +| **Thing ID** | The ID of the `Thing` that should receive or is sending the message | +| **Subject** | A custom topic string (for example, `turnOff`, `smokeDetected`) | +Optional fields: -## Receiving Messages +| Field | Description | +|-------|-------------| +| **Feature ID** | Target a specific [Feature](basic-feature.html) instead of the whole Thing | +| **content-type** | MIME type of the payload (for example, `application/json`) | +| **correlation-id** | Required if you want Ditto to route a response back to the sender | -To be able to receive Messages for a Thing, you need to have `READ` access on that Thing. -When a Message is sent to or from a Thing, **every** connected WebSocket, [SSE](httpapi-sse.html) or -[connection target](basic-connections.html#targets) with the correct access rights will receive the Message. +## Payload -If there is more than one response to a message received by multiple consumers, only the -first response will be routed back to the initial issuer of a Message. +A message can carry any payload. Since Ditto does not interpret the content, you choose the +serialization format and content-type that fits your solution. -### Filtering when subscribing for messages +## Sending and receiving messages -In order to not receive all messages an [authenticated subject](basic-auth.html#authenticated-subjects) is entitled to -receive when subscribing for messages, but to filter for specific criteria, messages may be filtered on the Ditto -backend side before they are sent to a message receiver. +### Sending -#### Filtering messages by namespaces +You can send messages through: -Filtering messages may be done based on a namespace name. Each Ditto [Thing](basic-thing.html) has an ID containing a -namespace (see also the conventions for a [Thing ID](basic-thing.html#thing-id)). +* The [HTTP API](http-api-doc.html#/Messages) -- fire-and-forget or blocking (waits for a response) +* The [WebSocket API](httpapi-protocol-bindings-websocket.html) -- as [Ditto Protocol](protocol-overview.html) messages +* [Connection sources](basic-connections.html#sources) -- incoming Ditto Protocol messages via a managed connection -By providing the `namespaces` filter, a comma separated list of which namespaces to include in the result, only Things -in namespaces of interest are considered and thus only messages of these Things are published to the message receiver. +Sending requires `WRITE` permission on the Thing (see [Permissions](#permissions)). -For example, one would only subscribe for messages occurring in 2 specific namespaces by defining: -``` -namespaces=org.eclipse.ditto.one,org.eclipse.ditto.two -``` +### Receiving -#### Filtering messages by RQL expression +You can receive messages through: -If filtering by namespaces is not sufficient, Ditto also allows to provide an [RQL expression](basic-rql.html) -specifying: +* The [WebSocket API](httpapi-protocol-bindings-websocket.html) -- as Ditto Protocol messages +* [Server Sent Events (SSE)](httpapi-sse.html#thing-messages) +* [Connection targets](basic-connections.html#target-topics-and-filtering) -- subscribing for "Thing messages" -* an [enriched](basic-enrichment.html) Thing state based condition determining when messages should be delivered and when not -* a filter based on the fields of the [Ditto Protocol](protocol-specification.html) message which should be filtered, - e.g.: - * using the `topic:subject` placeholder as query property, filtering for the message's subject - and other filter options on the [Ditto Protocol topic](protocol-specification-topic.html) can be done - * using the `resource:path` placeholder as query property, filtering based on the affected - [Ditto Protocol path](protocol-specification.html#path) of a Ditto Protocol message can be done, e.g. targeting a - specific message addressed to a certain feature ID - * for all supported placeholders, please refer to the - [placeholders documentation](basic-placeholders.html#scope-rql-expressions-when-filtering-for-ditto-protocol-messages) +Receiving requires `READ` permission on the Thing. Every connected client with the correct +permissions receives the message. If multiple consumers respond, only the first response is +routed back to the original sender. +### Filtering messages -## Sending Messages +When subscribing for messages, you can filter to receive only the ones you care about. -If you want to send a Message to or from a Thing, you need `WRITE` permissions on that Thing. -Every WebSocket or [connection target](basic-connections.html#targets) that is able to receive Messages for the -Thing (`READ` permission), will receive your message. +**By namespace:** Provide a comma-separated list of namespaces: +```text +namespaces=org.eclipse.ditto.one,org.eclipse.ditto.two +``` -## Responding to Messages +**By RQL expression:** Use an [RQL expression](basic-rql.html) with +[enriched](basic-enrichment.html) Thing state or Ditto Protocol field +[placeholders](basic-placeholders.html#scope-rql-expressions-when-filtering-for-ditto-protocol-messages): -Since messages are stateless there is no *direct* response to a Message. +* `topic:subject` -- filter by message subject +* `resource:path` -- filter by the affected resource path -For Ditto to be able to route the response of a Message back to the issuer, the -correlation-ids need to match. E.g. when the sender uses correlation-id `random-aa98s`, -any receiver can reply by using the same correlation-id `random-aa98s`. +## Responding to messages +Messages are stateless, so there is no built-in request-response mechanism. To route a response +back to the sender, use the same `correlation-id` in the reply message. ## Permissions -### API version 2 +To control message access, configure the `message:/` resource in the Thing's +[Policy](basic-policy.html#message-resources): -Permissions in API version 2 can be defined fine-grained. In order to be able to receive all Messages of a Thing, -you need `READ` permission for the `message:/` resource in the used [Policy](basic-policy.html#message).
    -There can however be specified that you may only receive specific Messages (with a defined subject), also -expressed via [Policy entry](basic-policy.html#message).
    -The same applies for being able to send Messages, here a `WRITE` permission is required either globally for -all messages via the `message:/` resource or only for specific ones. +* `READ` on `message:/` -- receive all messages for the Thing +* `WRITE` on `message:/` -- send messages to the Thing +* Fine-grained control -- grant access to specific subjects or features (for example, + `message:/features/lamp/inbox/messages/turnOff`) -There is one sole exception, which are [Claim Messages](#claim-messages). You do -not need the access rights for sending them. +### Claim messages +Claim messages are a special case for gaining initial access to a Thing: -## Claim Messages +* The subject is always `claim` +* You do **not** need `WRITE` permission to send a claim message +* Ditto forwards it to all subscribers registered for claim messages on that Thing +* The receiver decides whether to grant access (for example, by updating the Policy) -A Claim Message is used to gain access to a Thing. For this purpose a Claim Messages has two specifics: -* the predefined message subject is always *claim* -* you do not require `WRITE` permission to send a Claim Message +## Further reading -Apart from that the Claim Message is handled like a standard Message. It is forwarded to all Ditto Protocol bindings -that registered for Claim Messages of the specific Thing. The decision whether to grant access (by setting permissions) -is completely up to the receiver of the Claim Message e.g. after verifying the payload of the Message. +* [Policies](basic-policy.html) -- configure message permissions +* [Change Notifications](basic-changenotifications.html) -- subscribe to state change events +* [Messages HTTP API](http-api-doc.html#/Messages) -- HTTP endpoint reference +* [Ditto Protocol messages specification](protocol-specification-things-messages.html) -- wire format diff --git a/documentation/src/main/resources/pages/ditto/basic-metadata.md b/documentation/src/main/resources/pages/ditto/basic-metadata.md index 72af4c09aa3..001e1468f4d 100644 --- a/documentation/src/main/resources/pages/ditto/basic-metadata.md +++ b/documentation/src/main/resources/pages/ditto/basic-metadata.md @@ -1,200 +1,172 @@ --- -title: Thing Metadata +title: Metadata keywords: metadata, things, model, semantic tags: [model] permalink: basic-metadata.html --- -A Thing in Ditto is also able to store metadata information, e.g. about single -[feature properties](basic-feature.html#feature-properties), complete features and also [attributes](basic-thing.html#attributes) -or other data stored in a digital twin ([thing](basic-thing.html)). +Metadata lets you attach contextual information to any part of a Thing -- for example, recording +when a value was last updated, who changed it, or what unit of measurement it uses. -This metadata can contain additional information which shall not be treated as part of the twin's value, however may -be useful to provide some context of the twin's data. +{% include callout.html content="**TL;DR**: Metadata is extra information attached to Thing attributes and feature +properties. You set it via the `put-metadata` header, read it via field selectors or the `get-metadata` +header, and delete it via the `delete-metadata` header." type="primary" %} -Metadata has not its own API but can only be modified, retrieved or deleted while modifying the state of a twin as a side effect.
    -By default, metadata is not returned on API requests, but must be [asked for explicitly](#reading-metadata-information). +## What is metadata for? -An example is the timestamp when the current value of e.g. a feature property was updated for the last time. -Or the metadata information of a feature property may contain information about its type or its semantics (e.g. a unit -of measurement). +Metadata provides context about your twin's data without becoming part of the data itself. Common +use cases include: +* **Timestamps** -- when a property was last updated +* **Audit trails** -- who changed a value +* **Semantic annotations** -- units of measurement, data type descriptions +* **Change tracking** -- recording the source system that provided a value -## Modifying metadata +Metadata is **not** returned by default. You must request it explicitly. -Modifying arbitrary `metadata` is possible by using the `put-metadata` header -(e.g. for HTTP requests, set it as HTTP header, for Ditto Protocol requests, put it in the `"headers"` section of the -protocol message), see [here for an overview of the available headers](protocol-specification.html#headers). +## How it works -The value of the `put-metadata` is a JSON array containing JSON objects with `"key"` and `"value"` parts: -* `"key"`: describes the hierarchical position in the Thing where the metadata should be placed -* `"value"`: is an arbitrary JSON value to set as metadata (could also be a nested JSON object) +Metadata mirrors the structure of the Thing it describes. If your Thing has a property at +`features/lamp/properties/color/r`, the metadata for that property lives at the same path +inside the `_metadata` object. -The `put-metadata` header can be specified at all API levels and will then modify the metadata object at the same level, -relative to the request. For example a `PUT` on a feature property `color` with a `put-metadata` header `"key"` -`/r/changeLog` will only set the metadata for this sub property. +Metadata has no dedicated API endpoint. Instead, you modify, read, and delete it using HTTP headers +as a side effect of regular Thing operations. -### Example for setting metadata +## Setting metadata + +Use the `put-metadata` header on any modifying request (`PUT`, `PATCH`, or via the Ditto Protocol +`"headers"` section). The value is a JSON array of objects, each with a `"key"` and `"value"`: + +```json +[ + { + "key": "/features/lamp/properties/color/description", + "value": "Color represented with RGB values" + }, + { + "key": "/features/lamp/properties/status/description", + "value": "Status of the lamp" + } +] +``` + +### Example: setting metadata on a Thing update + +When you update your twin's lamp color: -Assuming you modify your twin's lamp color with a call: ```json { "thingId": "org.eclipse.ditto:my-lamp-1", "features": { "lamp": { "properties": { - "color": { - "r": 100, - "g": 0, - "b": 255 - }, - "status": { - "on": "true" - } - } - } + "color": { "r": 100, "g": 0, "b": 255 }, + "status": { "on": "true" } + } + } } } ``` -In case you want to set metadata on all properties of a feature (`"r"`, `"g"` and `"b"`) plus some -extra metadata to only set for the `"color"` property.
    -The content of the `put-metadata` in order to do that would look like this: +Send the `put-metadata` header with a description for each property group: ```json [ { - "key": "*/changeLog", - "value": { - "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name":"ditto", - "mail":"ditto@mail.com" - } - } + "key": "features/lamp/properties/color/description", + "value": "Color represented with RGB values" }, { - "key": "/features/lamp/properties/color/description", - "value": "Color represented with RGB values" + "key": "features/lamp/properties/status/description", + "value": "Status of the lamp" } ] ``` -The resulting Thing JSON including its `_metadata` would look like this: +The resulting `_metadata` on the Thing: + ```json { - "thingId": "org.eclipse.ditto:my-lamp-1", + "_metadata": { "features": { "lamp": { "properties": { "color": { - "r": 100, - "g": 0, - "b": 255 + "description": "Color represented with RGB values" }, "status": { - "on": "true" - } - } - } - }, - "_metadata": { - "features": { - "lamp": { - "properties": { - "color": { - "r": { - "changeLog": { - "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } - } - }, - "g": { - "changeLog": { - "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } - } - }, - "b": { - "changeLog": { - "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } - } - }, - "description": "Color represented with RGB values" - } + "description": "Status of the lamp" } } } } + } } ``` -Another example for the `put-metadata` header can look like this where we want to add a "description" to both feature properties. +### Combining wildcards with absolute paths + +You can mix wildcard keys and absolute-path keys in a single `put-metadata` array. For example, to +add a change log to every leaf **and** a description to the `color` property: ```json [ - { - "key": "features/lamp/properties/color/description", - "value": "Color represented with RGB values" + { + "key": "*/changeLog", + "value": { + "changedAt": "2022-08-02T04:30:07", + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } + } }, - { - "key": "features/lamp/properties/status/description", - "value": "Status of the lamp" - } + { + "key": "/features/lamp/properties/color/description", + "value": "Color represented with RGB values" + } ] ``` -The resulting Thing JSON with the previously added metadata would look like this: +The resulting `_metadata` contains both the per-leaf `changeLog` entries and the absolute-path +`description`: + ```json { - "thingId": "org.eclipse.ditto:my-lamp-1", + "_metadata": { "features": { "lamp": { "properties": { "color": { - "r": 100, - "g": 0, - "b": 255 - }, - "status": { - "on": "true" - } - } - } - }, - "_metadata": { - "features": { - "lamp": { - "properties": { - "color": { - "description": "Color represented with RGB values" + "r": { + "changeLog": { + "changedAt": "2022-08-02T04:30:07", + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } + } }, - "status": { - "description": "Status of the lamp" - } + "g": { + "changeLog": { + "changedAt": "2022-08-02T04:30:07", + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } + } + }, + "b": { + "changeLog": { + "changedAt": "2022-08-02T04:30:07", + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } + } + }, + "description": "Color represented with RGB values" } } } } + } } ``` -### Modifying metadata on all JSON leaves +### Setting metadata on all JSON leaves -A special syntax for the key is `*/{key}` which means that all JSON leaves of the modify operation will -get the metadata key `{key}` with the given value. So if, for example, only the affected JSON leaves should -get the timestamp where the changed values were recorded, one would set the `put-metadata` header as shown in the -following example: +Use the special key syntax `*/{key}` to set the same metadata on every leaf value affected by your +update. For example, to record a change log on all modified properties: ```json [ @@ -202,33 +174,17 @@ following example: "key": "*/changeLog", "value": { "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name":"ditto", - "mail":"ditto@mail.com" - } + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } } ] ``` -The resulting Thing JSON including its `_metadata` would look like this: +This adds a `changeLog` entry to each individual leaf property (`r`, `g`, `b`, `on`) rather than +to the parent objects. The resulting `_metadata` would look like: + ```json { - "thingId": "org.eclipse.ditto:my-lamp-1", - "features": { - "lamp": { - "properties": { - "color": { - "r": 100, - "g": 0, - "b": 255 - }, - "status": { - "on": "true" - } - } - } - }, "_metadata": { "features": { "lamp": { @@ -237,39 +193,27 @@ The resulting Thing JSON including its `_metadata` would look like this: "r": { "changeLog": { "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } }, "g": { "changeLog": { "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } }, "b": { "changeLog": { "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } - }, - "status": { - "on": { - "changeLog": { - "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } - } + } + }, + "status": { + "on": { + "changeLog": { + "changedAt": "2022-08-02T04:30:07", + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } } } @@ -280,23 +224,19 @@ The resulting Thing JSON including its `_metadata` would look like this: } ``` -## Reading metadata information +## Reading metadata -Metadata of a Thing can be retrieved by querying a full thing, e.g. via the [HTTP API](http-api-doc.html), and -specifying an (additional) [field selector](httpapi-concepts.html#with-field-selector) `_metadata`, -e.g.: `?fields=thingId,attributes,_metadata`. +You have two ways to retrieve metadata. -Metadata can also be queried by using the `get-metadata` header -(e.g. for HTTP requests, set it as HTTP header, for Ditto Protocol requests, put it in the `"headers"` section of the -protocol message), see [here for an overview of the available headers](protocol-specification.html#headers). -The `get-metadata` header expects a comma separated list of metadata `{key}`. This will return the relative metadata -in the `ditto-metadata` header. +### Using field selectors -### Example for reading metadata with field selector +Add `_metadata` to the `fields` query parameter: -For example a `GET` request to -`https://{ditto-instance}/api/2/things/{namespace}:{name}?fields=thingId,policyId,features,_created,_modified,_revision,_metadata` -will yield the metadata stored for the given Thing, in the following format: +```bash +GET /api/2/things/org.eclipse.ditto:my-lamp-1?fields=thingId,policyId,features,_created,_modified,_revision,_metadata +``` + +This returns the Thing along with its full `_metadata` object: ```json { @@ -305,14 +245,8 @@ will yield the metadata stored for the given Thing, in the following format: "features": { "lamp": { "properties": { - "color": { - "r": 0, - "g": 255, - "b": 255 - }, - "status": { - "on": "true" - } + "color": { "r": 0, "g": 255, "b": 255 }, + "status": { "on": "true" } } } }, @@ -326,29 +260,20 @@ will yield the metadata stored for the given Thing, in the following format: "color": { "r": { "changeLog": { - "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } + "changedAt": "2022-08-02T04:30:07", + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } }, "g": { "changeLog": { "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } }, "b": { "changeLog": { - "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } + "changedAt": "2022-08-02T04:30:07", + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } }, "description": "Color represented with RGB values" @@ -357,10 +282,7 @@ will yield the metadata stored for the given Thing, in the following format: "on": { "changeLog": { "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } }, "description": "Status of the Lamp" @@ -372,10 +294,13 @@ will yield the metadata stored for the given Thing, in the following format: } ``` -### Example for reading metadata with header +### Using the get-metadata header + +Set the `get-metadata` HTTP header to a comma-separated list of metadata paths. Ditto returns +the matching metadata in the `ditto-metadata` response header. -For example a `GET` request to `https://{ditto-instance}/api/2/things/{namespace}:{name}` with HTTP header `get-metadata` -and value `features/lamp/properties/color/r` will return the following content in the `ditto-metadata` header: +For example, to get the metadata for a specific leaf property, set the header +`get-metadata: features/lamp/properties/color/r`. The `ditto-metadata` response header contains: ```json { @@ -386,10 +311,7 @@ and value `features/lamp/properties/color/r` will return the following content i "r": { "changeLog": { "changedAt": "2022-08-02T04:30:07", - "changedBy": { - "name": "ditto", - "mail": "ditto@mail.com" - } + "changedBy": { "name": "ditto", "mail": "ditto@mail.com" } } } } @@ -399,9 +321,8 @@ and value `features/lamp/properties/color/r` will return the following content i } ``` -Metadata can also be queried using a wildcard which expands the level where it is specified, e.g. a `GET` request -to `https://{ditto-instance}/api/2/things/{namespace}:{name}` with HTTP header `get-metadata` and value -`features/lamp/properties/*/description` will return: +You can use wildcards to expand one level. For example, +`get-metadata: features/lamp/properties/*/description` returns: ```json { @@ -420,44 +341,47 @@ to `https://{ditto-instance}/api/2/things/{namespace}:{name}` with HTTP header ` } ``` -## Deleting metadata information +## Deleting metadata -Metadata can be deleted by using the `delete-metadata` header with modifying requests (`PUT` or `PATCH`). -For HTTP requests, set it as HTTP header, for Ditto Protocol requests, put it in the `"headers"` section of the -protocol message, see [here for an overview of the available headers](protocol-specification.html#headers). -The `delete-metadata` header expects a comma separated list of metadata `{key}`. +Use the `delete-metadata` header on a modifying request (`PUT` or `PATCH`). Provide a +comma-separated list of metadata paths to remove. -For example a `PATCH` request to `https://{ditto-instance}/api/2/things/{namespace}:{name}` with HTTP header `delete-metadata` -and value `features/lamp/properties/color` will remove the complete `color` property from the thing metadata. +For example, to remove all color metadata: -Note: When deleting things or parts of a thing, like feature properties or attributes, their relative metadata +``` +delete-metadata: features/lamp/properties/color +``` + +When you delete a Thing or part of a Thing (like a feature or attribute), the associated metadata is also deleted. -## Wildcard usage for metadata requests - -When working with metadata there are some wildcards which can be used to modify, retrieve or delete metadata. -The following table gives an overview which Wildcards can be used on top-level for what requests. - -| Wildcard | PUT/PATCH | GET | DELETE | -|-----------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------| -| `*` | x | retrieve all metadata relative to the path | delete all metadata relative to the path | -| `*/key` | add metadata for the given `key` to all JSON leaves | retrieve all metadata with `key` | delete all metadata with `key` | -| `attributes/*/key` | add metadata for the given `key` to all attributes | retrieve metadata for given `key` from all attributes | delete metadata for given `key` from all attributes | -| `features/*/properties/*/key` | add metadata for the given `key` to all feature properties | retrieve metadata for given `key` from all feature properties | delete metadata for given `key` from all feature properties | -| `features/*/properties/{property}/key` | add metadata for the given `key` to all features with a given property | retrieve metadata for given `key` from all features with a specific property | delete metadata for given `key` from all features with a specific property | -| `features/{feature}/properties/*/key` | add metadata for the given `key` to all properties of a feature | retrieve metadata for given `key` from all properties of a feature | delete metadata for given `key` from all properties of a feature | - -Wildcards can also be used on the /features resource: - -| Wildcard | PUT/PATCH | GET | DELETE | -|-------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------| -| `*/properties/*/key` | add metadata for the given `key` to all feature properties | retrieve metadata for given `key` from all feature properties | delete metadata for given `key` from all feature properties | -| `*/properties/{property}/key` | add metadata for the given `key` to all features with a given property | retrieve metadata for given `key` from all features with a specific property | delete metadata for given `key` from all features with a specific property | -| `{feature}/properties/*/key` | add metadata for the given `key` to all properties of a feature | retrieve metadata for given `key` from all properties of a feature | delete metadata for given `key` from all properties of a feature | - -Note: With the `*` wildcard it is only possible to skip one level in the resource path. You can not use the wildcard `*` other than in the examples above. - -## Multiple metadata header -It is not possible to use multiple metadata headers in one request. For GET requests it is only possible to use the `get-metadata`header. -For PUT/PATCH requests it is only possible to use either the `put-metadata` or `delete-metadata` header. -In case multiple headers are used in a request an exception will be the result. \ No newline at end of file +## Wildcard reference + +| Wildcard | PUT/PATCH | GET | DELETE | +|----------|-----------|-----|--------| +| `*` | -- | Retrieve all metadata relative to the path | Delete all metadata relative to the path | +| `*/key` | Add `key` metadata to all JSON leaves | Retrieve all metadata with `key` | Delete all metadata with `key` | +| `attributes/*/key` | Add `key` to all attributes | Retrieve `key` from all attributes | Delete `key` from all attributes | +| `features/*/properties/*/key` | Add `key` to all feature properties | Retrieve `key` from all feature properties | Delete `key` from all feature properties | +| `features/*/properties/{prop}/key` | Add `key` to `{prop}` in all features | Retrieve `key` from `{prop}` in all features | Delete `key` from `{prop}` in all features | +| `features/{feat}/properties/*/key` | Add `key` to all properties of `{feat}` | Retrieve `key` from all properties of `{feat}` | Delete `key` from all properties of `{feat}` | + +The same wildcards work at the `/features` resource level, using relative paths +(`*/properties/*/key`, `{feature}/properties/*/key`, etc.). + +The `*` wildcard skips exactly one level in the path. It cannot be used in other positions. + +## Constraints + +You can use only one metadata header per request: +* For GET requests: `get-metadata` only +* For PUT/PATCH requests: either `put-metadata` or `delete-metadata`, but not both + +Using multiple metadata headers in one request results in an error. + +## Further reading + +* [Protocol headers reference](protocol-specification.html#headers) -- all available Ditto headers +* [Things](basic-thing.html) -- the entity that metadata attaches to +* [HTTP API concepts](httpapi-concepts.html#field-selectors) -- field selectors for partial + retrieval diff --git a/documentation/src/main/resources/pages/ditto/basic-namespaces-and-names.md b/documentation/src/main/resources/pages/ditto/basic-namespaces-and-names.md index 573b1d64f9f..a08a0d919ab 100644 --- a/documentation/src/main/resources/pages/ditto/basic-namespaces-and-names.md +++ b/documentation/src/main/resources/pages/ditto/basic-namespaces-and-names.md @@ -1,70 +1,76 @@ --- -title: Namespaces and Names +title: Namespaces & Names keywords: namespace, name, id, entity, model, regex tags: [model] permalink: basic-namespaces-and-names.html --- -Ditto uses namespaces and names for the IDs of important entity types like Things or Policies. Due to the fact that -those IDs often need to be set in the path of HTTP requests, we have restricted the set of allowed characters. +Ditto uses namespaced identifiers for Things, Policies, and other entities. Every ID combines a +namespace and a name separated by a colon. + +{% include callout.html content="**TL;DR**: Entity IDs follow the format `namespace:name`, with a maximum length of +256 characters. Namespaces use dot-separated segments (like Java packages), and names can contain +most characters except slashes and control characters." type="primary" %} ## Namespace -The namespace must conform to the following notation: -* must start with a lower- or uppercase character from a-z -* may use dots (`.`) or dashes (`-`) to separate characters -* a dot or dash must be followed by a lower- or uppercase character from a-z -* numbers may be used -* underscore may be used - -When writing a Java application, you can use the following regex to validate your namespaces: - ``(?|(?:(?:[a-zA-Z]\w*+)(?:[.-][a-zA-Z]\w*+)*+))`` - (see [RegexPatterns#NAMESPACE_REGEX](https://github.com/eclipse-ditto/ditto/blob/master/base/model/src/main/java/org/eclipse/ditto/base/model/entity/id/RegexPatterns.java)). - -Examples for valid namespaces: -* `org.eclipse.ditto`, -* `com.some-domain`, -* `com.google`, +The namespace identifies the organizational scope for an entity. It must: + +* Start with a letter (`a-z` or `A-Z`) +* Use dots (`.`) or dashes (`-`) to separate segments, each starting with a letter +* Contain only letters, digits, and underscores within segments + +**Valid namespaces:** +* `org.eclipse.ditto` +* `com.some-domain` +* `com.google` * `foo.bar_42` +**Regex (Java):** +``(?|(?:(?:[a-zA-Z]\w*+)(?:[.-][a-zA-Z]\w*+)*+))`` +(see [RegexPatterns#NAMESPACE_REGEX](https://github.com/eclipse-ditto/ditto/blob/master/base/model/src/main/java/org/eclipse/ditto/base/model/entity/id/RegexPatterns.java)) + ## Name -The name must conform to the following notation: -* may not be empty -* may not contain `/` (slash) -* may not contain control characters +The name identifies the entity within its namespace. It must: -When writing a Java application, you can use the following regex to validate your thing name: - ``(?[^\x00-\x1F\x7F-\xFF/]++)`` - (see [RegexPatterns#ENTITY_NAME_REGEX](https://github.com/eclipse-ditto/ditto/blob/master/base/model/src/main/java/org/eclipse/ditto/base/model/entity/id/RegexPatterns.java)). +* Not be empty +* Not contain `/` (slash) +* Not contain control characters -Examples for valid names: - * `ditto`, - * `smart-coffee-1`, - * `foo%2Fbar` - * `foo bar` - * `foo+bar%20` +**Valid names:** +* `ditto` +* `smart-coffee-1` +* `foo%2Fbar` +* `foo bar` +* `foo+bar%20` -## Namespaced ID +**Regex (Java):** +``(?[^\x00-\x1F\x7F-\xFF/]++)`` +(see [RegexPatterns#ENTITY_NAME_REGEX](https://github.com/eclipse-ditto/ditto/blob/master/base/model/src/main/java/org/eclipse/ditto/base/model/entity/id/RegexPatterns.java)) -A namespaced ID must conform to the following expectations: -* namespace and name separated by a `:` (colon) -* have a maximum length of 256 characters +## Namespaced ID -When writing a Java application, you can use the following regex to validate your namespaced IDs: - ``(?|(?:(?:[a-zA-Z]\w*+)(?:[.-][a-zA-Z]\w*+)*+)):(?[^\x00-\x1F\x7F-\xFF/]++)`` - (see [RegexPatterns#ID_REGEX](https://github.com/eclipse-ditto/ditto/blob/master/base/model/src/main/java/org/eclipse/ditto/base/model/entity/id/RegexPatterns.java)). +A complete entity ID joins the namespace and name with a colon (`:`). The combined ID must not +exceed 256 characters. -Examples for valid IDs: -* `org.eclipse.ditto:smart-coffee-1`, -* `foo:bar`, -* `org.eclipse.ditto_42:smart-coffeee`, -* `com.some-domain.ditto-rocks:foobar`, -* `org.eclipse:admin-policy`, +**Valid IDs:** +* `org.eclipse.ditto:smart-coffee-1` +* `foo:bar` +* `org.eclipse.ditto_42:smart-coffeee` +* `com.some-domain.ditto-rocks:foobar` +* `org.eclipse:admin-policy` * `org.eclipse:admin policy` +**Regex (Java):** +``(?|(?:(?:[a-zA-Z]\w*+)(?:[.-][a-zA-Z]\w*+)*+)):(?[^\x00-\x1F\x7F-\xFF/]++)`` +(see [RegexPatterns#ID_REGEX](https://github.com/eclipse-ditto/ditto/blob/master/base/model/src/main/java/org/eclipse/ditto/base/model/entity/id/RegexPatterns.java)) + ## Encoding and decoding -If hex encoded characters or spaces are used in the Thing name, the protocol dependent de- or encoding must be -taken into account. If a Thing is created with the ID `eclipse.ditto:foo bar` and is to be queried via the HTTP API, -the space must be encoded accordingly: `GET /things/eclipse.ditto:foo%20bar`. +When a Thing name contains spaces or special characters, you must URL-encode them in HTTP requests. +For example, if you create a Thing with the ID `eclipse.ditto:foo bar`, query it as: + +```bash +GET /things/eclipse.ditto:foo%20bar +``` diff --git a/documentation/src/main/resources/pages/ditto/basic-overview.md b/documentation/src/main/resources/pages/ditto/basic-overview.md index 466386fdc99..5b1f80525c9 100644 --- a/documentation/src/main/resources/pages/ditto/basic-overview.md +++ b/documentation/src/main/resources/pages/ditto/basic-overview.md @@ -1,33 +1,36 @@ --- -title: Basic concepts overview +title: Data Model Overview keywords: basic concepts, overview, thing, feature, domain model, model tags: [model] permalink: basic-overview.html --- -## Domain model +Ditto's data model organizes IoT device data into a hierarchy of Things, Features, and Policies. -Eclipse Ditto does not claim to know exactly which structure Things in the -IoT have or should have.
    -Its idea is to be as agnostic as possible when it comes to `Thing` data. +{% include callout.html content="**TL;DR**: A Thing has attributes (static metadata) and features (dynamic state). A +Policy controls who can read and write each part. That is the entire data model." type="primary" %} -Nevertheless, two coarse elements are defined in order to structure `Thing`s (see also [Thing](basic-thing.html)): -* Attributes: intended for managing static metadata of a `Thing` - as JSON object - which does not change frequently. -* [Features](basic-feature.html): intended for managing state data (e.g. sensor data or configuration data) of a `Thing`. +## How the model works -## API version 2 +Ditto does not enforce a specific schema for your +IoT data. Instead, it provides +two structural elements inside each [Thing](basic-thing.html): -In API version 2 the information which _subjects_ are allowed to READ, WRITE Things are managed separately via -[Policies](basic-policy.html).
    -The `Thing` only contains a `policyId` which links to a Policy containing the authorization information. -This class diagram shows the structure Ditto requires for **API version 2**: +* **Attributes** -- static metadata about the `Thing` (location, serial number, manufacturer) stored as + a JSON object. These values do not change frequently. +* **[Features](basic-feature.html)** -- dynamic state data (sensor readings, configuration, + operational status). Each Feature groups related properties under a named identifier. + +Access control is managed separately through [Policies](basic-policy.html). The `Thing` references its +Policy by `policyId`, and that Policy defines which authenticated subjects may read or write the +`Thing` or parts of it. {% include image.html file="pages/basic/ditto-class-diagram-v2.png" alt="Ditto Class Diagram" - caption="Class diagram of Ditto's most basic entities in API version 2." max-width=600 %} + caption="Class diagram of Ditto's core entities." max-width=600 %} -### JSON Format +## Minimal Thing -In **API version 2** the most minimalistic representation of a Thing is for example the following: +The smallest valid Thing contains only an ID and a reference to its Policy: ```json { @@ -36,10 +39,11 @@ In **API version 2** the most minimalistic representation of a Thing is for exam } ``` -Attributes and Features are optional (as also shown in the class diagram above), so in the example JSON above they are -omitted. +Attributes and Features are optional. + +## A Thing with data -A minimalistic Thing with one attribute, one Feature, and a definition could look like this: +A more realistic Thing includes a definition, an attribute, and a feature: ```json { @@ -51,10 +55,17 @@ A minimalistic Thing with one attribute, one Feature, and a definition could loo }, "features": { "transmission": { - "properties": { - "cur_speed": 90 - } - } + "properties": { + "cur_speed": 90 + } + } } } ``` + +## Further reading + +* [Things](basic-thing.html) -- full details on Thing structure, IDs, and attributes +* [Features](basic-feature.html) -- properties, desired properties, and definitions +* [Policies](basic-policy.html) -- fine-grained access control for Things and Features +* [Namespaces & Names](basic-namespaces-and-names.html) -- naming conventions for entity IDs diff --git a/documentation/src/main/resources/pages/ditto/basic-placeholders.md b/documentation/src/main/resources/pages/ditto/basic-placeholders.md index 71ce63e2293..58dbaa51dd6 100644 --- a/documentation/src/main/resources/pages/ditto/basic-placeholders.md +++ b/documentation/src/main/resources/pages/ditto/basic-placeholders.md @@ -31,7 +31,7 @@ Which placeholder values are available depends on the context where the placehol | Placeholder | Description | |----------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `{%raw%}{{ thing-json: }}{%endraw%}` | Value (in string representation) of the JSON identified by the provided ''json-pointer'' in JsonPointer notation - e.g., `thing-json:attributes/location` for the "location" attribute or `thing-json:features/temperature/properties/value` for the temperature property. Also supported in the [migrate definition](httpapi-concepts.html#things-in-api-2---migrate-thing-definitions) migration payload: when the value is exactly one placeholder (brace or legacy, no pipeline), the resolved value preserves its JSON type (number, boolean, string, object, array); pipelines and multiple placeholders yield a string. | +| `{%raw%}{{ thing-json: }}{%endraw%}` | Value (in string representation) of the JSON identified by the provided ''json-pointer'' in JsonPointer notation - e.g., `thing-json:attributes/location` for the "location" attribute or `thing-json:features/temperature/properties/value` for the temperature property. Also supported in the [migrate definition](httpapi-concepts.html#things-endpoints---migrate-thing-definitions) migration payload: when the value is exactly one placeholder (brace or legacy, no pipeline), the resolved value preserves its JSON type (number, boolean, string, object, array); pipelines and multiple placeholders yield a string. | ### Feature Placeholder diff --git a/documentation/src/main/resources/pages/ditto/basic-policy.md b/documentation/src/main/resources/pages/ditto/basic-policy.md index 99c24e7d72b..5fea6d18d07 100644 --- a/documentation/src/main/resources/pages/ditto/basic-policy.md +++ b/documentation/src/main/resources/pages/ditto/basic-policy.md @@ -802,8 +802,8 @@ namespace org.eclipse.ditto (the pattern requires at least one sub- ## Tools for editing a Policy -The Policy can be edited with a text editor of your choice. -Just make sure it is in valid JSON representation, and that at least one valid subject is granted write permission at +You can edit the Policy with any text editor. +Make sure it is valid JSON and that at least one valid subject is granted write permission at the root resources. {% @@ -817,7 +817,7 @@ granted or revoked the permissions your use case is supposed to support. ## Example -Given you need to support the following scenario: +Consider this access control scenario: * Owner: The Thing *my.namespace:thing-0123* is owned by a user. Thus, she needs full access and admin rights for the complete Thing. diff --git a/documentation/src/main/resources/pages/ditto/basic-rql.md b/documentation/src/main/resources/pages/ditto/basic-rql.md index 32c1fd82d99..cbaac6e8349 100644 --- a/documentation/src/main/resources/pages/ditto/basic-rql.md +++ b/documentation/src/main/resources/pages/ditto/basic-rql.md @@ -5,24 +5,21 @@ tags: [rql, protocol] permalink: basic-rql.html --- -Ditto utilizes a subset of RQL -as language for specifying queries. +You query Ditto using a subset of Resource Query Language (RQL) for specifying queries, filters, and conditions. -The [RQL project page](https://github.com/persvr/rql) says about it: +{% include callout.html content="**TL;DR**: RQL provides nestable operators like `eq()`, `gt()`, `like()`, and `exists()` combined with `and()`, `or()`, `not()`. Use it for search queries, change notification filters, and conditional requests." type="primary" %} -> Resource Query Language (RQL) is a query language designed for use in URIs with object style data structures. [...]
    -RQL can be thought as basically a set of nestable named operators which each have a set of arguments. -RQL is designed to have an extremely simple, but extensible grammar that can be written in a URL friendly query string. +## Overview -An example helps more than a thousand words, so that would be the example of a simple RQL query querying -for `foo="ditto"` and `bar<10`: -``` +RQL is a query language designed for use in URIs with object-style data structures (see the [RQL project page](https://github.com/persvr/rql)). + +Here is a simple RQL query that filters for `foo="ditto"` and `bar<10`: + +```text and(eq(foo,"ditto"),lt(bar,10)) ``` -That query consists of one [logical operator ](#logical-operators) ["and"](#and), -two [relational operators](#relational-operators) of which each consists of a [property](#query-property) -and a [value](#query-value). +This query consists of one [logical operator](#logical-operators) (`and`) containing two [relational operators](#relational-operators), each with a [property](#query-property) and a [value](#query-value). The following sections describe what the RQL syntax is capable of and which RQL operators are supported in Eclipse Ditto. @@ -31,244 +28,230 @@ Eclipse Ditto. `eq(foo,3)`, is supported by Eclipse Ditto and that Ditto added more non-specified operators, e.g. `like`, `exists` and `empty`." %} - ## RQL filter The RQL filter specifies "what" to filter. ### Query property -``` + +```text = url-encoded-string ``` -When not starting with a prefix `:`, the RQL query property specifies a field in the JSON representation -of a [Thing](basic-thing.html#api-version-2). -For example a query property `thingId` selects the Thing ID as property, a query property `attributes/location` selects -an attribute with the name `location` as query property. +A query property specifies a field in the JSON representation of a [Thing](basic-thing.html#model-specification). For example, `thingId` selects the Thing ID, and `attributes/location` selects the `location` attribute. -To filter nested properties, Ditto uses the JSON Pointer notation ([RFC-6901](https://tools.ietf.org/html/rfc6901)), -where the property can also start with a slash `/` or omit it, so those 2 query properties are semantically the same: +Ditto uses JSON Pointer notation ([RFC-6901](https://tools.ietf.org/html/rfc6901)) for nested properties. You can include or omit the leading slash -- these are equivalent: * `/attributes/location` * `attributes/location` -The following example shows how to apply a filter for the sub property `location` of the parent property `attributes` -with a forward slash as separator: -``` +**Example** -- filter by a nested attribute: + +```text eq(attributes/location,"kitchen") ``` #### Placeholders as query properties -When using an RQL expression in order to e.g. filter for certain -[change notifications](basic-changenotifications.html#by-rql-expression), the query property might be a -[placeholder](basic-placeholders.html#scope-rql-expressions-when-filtering-for-ditto-protocol-messages) instead of a -field in JSON representation inside the Thing. +When you use RQL to filter [change notifications](basic-changenotifications.html#filter-by-rql-expression), you can use [placeholders](basic-placeholders.html#scope-rql-expressions-when-filtering-for-ditto-protocol-messages) instead of Thing JSON fields: -Currently supported placeholders for RQL expressions are: * `topic:` * `resource:` * `time:` -The [placeholder](basic-placeholders.html#scope-rql-expressions-when-filtering-for-ditto-protocol-messages) -documentation describes which placeholder names are supported. - +See the [placeholder documentation](basic-placeholders.html#scope-rql-expressions-when-filtering-for-ditto-protocol-messages) for supported names. ### Query value -``` + +```text = , , , true, false, null = , = "url-encoded-string", 'url-encoded-string' = time:now, time:now_epoch_millis ``` -String values may either be delimited using single or double quotes. +String values can use single or double quotes. -**Comparison of string values** +**String comparison** -{% include note.html content="Comparison operators such as `gt`, `ge`, `lt` and `le`, do not support a special +{% include note.html content="Comparison operators such as `gt`, `ge`, `lt` and `le`, do not support a special \"semantics\" of string comparison (e.g. regarding alphabetical or lexicographical ordering).
    - However, you can rely on the alphabetical sorting of strings with the same length (e.g. \"aaa\" < \"zzz\") and that the + However, you can rely on the alphabetical sorting of strings with the same length (e.g. \"aaa\" < \"zzz\") and that the order stays the same over multiple/different filter requests." %} - -**Comparison of other data types** -{% include note.html content="Please note that the comparison of other data types is supported by the API, but it - only supports comparison of same data types, and does not do any conversion during comparison." %} +**Other data types** +{% include note.html content="Please note that the comparison of other data types is supported by the API, but it + only supports comparison of same data types, and does not do any conversion during comparison." %} ### Relational operators -The following relational operators are supported. - #### eq + Filter property values equal to ``. -``` +```text eq(,) ``` -**Example - filter things owned by "SID123"** -``` +**Example** -- filter Things owned by "SID123": + +```text eq(attributes/owner,"SID123") ``` #### ne + Filter property values not equal to ``. -``` +```text ne(,) -``` - -**Example - filter things with owner different than "SID123"** ``` + +**Example** -- filter Things with owner different from "SID123": + +```text ne(attributes/owner,"SID123") ``` -The response will contain only things which **do** provide an owner attribute (in this case with value 0 or not SID123). +The response only contains Things that have an owner attribute (with a value other than "SID123"). #### gt -Filter property values greater than a ``. -``` -gt(,) -``` +Filter property values greater than ``. -**Example - filter things with thing ID greater than "A000"** +```text +gt(,) ``` + +**Example**: + +```text gt(thingId,"A000") ``` #### ge -Filter property values greater than or equal to a ``. -``` -ge(,) -``` +Filter property values greater than or equal to ``. -**Example - filter things with thing ID "A000" or greater** +```text +ge(,) ``` + +**Example**: + +```text ge(thingId,"A000") ``` #### lt -Filter property values less than a ``. +Filter property values less than ``. + +```text +lt(,) ``` -lt(,) -``` - -**Example - filter things with thing ID lower than "A000"** -``` + +**Example**: + +```text lt(thingId,"A000") ``` #### le -Filter property values less than or equal to a ``. -``` -le(,) -``` +Filter property values less than or equal to ``. -**Example - filter things with thing ID "A000" or lower** +```text +le(,) ``` + +**Example**: + +```text le(thingId,"A000") ``` #### in -Filter property values which contains at least one of the listed ``s. -``` -in(,,, ...) -``` +Filter property values matching at least one of the listed values. -**Example - filter things with thing ID "A000" or "AB00" or "AZ99"** +```text +in(,,, ...) ``` + +**Example** -- filter Things with ID "A000", "AB00", or "AZ99": + +```text in(thingId,"A000","AB00","AZ99") ``` #### like -Filter property values which are like (similar) a ``. -``` -like(,) +Filter property values matching a pattern (Ditto-specific operator). + +```text +like(,) ``` {% include note.html content="The `like` operator is not defined in the linked RQL grammar, it is a Ditto specific operator." %} -**Details concerning the like-operator** +Supported pattern expressions: +* `*endswith` -- match at the end of a string +* `startswith*` -- match at the beginning of a string +* `*contains*` -- match if a string contains the pattern +* `Th?ng` -- match a single wildcard character -The `like` operator provides some regular expression capabilities for pattern matching Strings. -The following expressions are supported: +**Examples**: -* \*endswith => match at the end of a specific String. -* startswith\* => match at the beginning of a specific String. -* \*contains\* => match if contains a specific String. -* Th?ng => match for a wildcard character. - -**Examples** -``` +```text like(attributes/key1,"*known-chars-at-end") - -like(attributes/key1,"known-chars-at-start*") - -like(attributes/key1,"*known-chars-in-between*") - +like(attributes/key1,"known-chars-at-start*") +like(attributes/key1,"*known-chars-in-between*") like(attributes/key1,"just-som?-char?-unkn?wn") ``` #### ilike -Filter property values which are like (similar) and case insensitive ``. -``` -ilike(,) +Filter property values matching a pattern, case-insensitively (Ditto-specific operator). + +```text +ilike(,) ``` {% include note.html content="The `ilike` operator is not defined in the linked RQL grammar, it is a Ditto specific operator." %} -**Details concerning the ilike-operator** - -The `ilike` operator provides some regular expression capabilities for pattern matching Strings with case insensitivity. - -The following expressions are supported: +Supports the same patterns as `like`, but ignores case: -* \*endswith => match at the end of a specific String. -* startswith\* => match at the beginning of a specific String. -* \*contains\* => match if contains a specific String. -* Th?ng => match for a wildcard character. - -**Examples** -``` +```text ilike(attributes/key1,"*known-CHARS-at-end") - -ilike(attributes/key1,"known-chars-AT-start*") - -ilike(attributes/key1,"*KNOWN-CHARS-IN-BETWEEN*") - +ilike(attributes/key1,"known-chars-AT-start*") +ilike(attributes/key1,"*KNOWN-CHARS-IN-BETWEEN*") ilike(attributes/key1,"just-som?-char?-unkn?wn") ``` #### exists -Filter property values which exist. +Filter for properties that exist (Ditto-specific operator). -``` +```text exists() ``` {% include note.html content="The `exists` operator is not defined in the linked RQL grammar, it is a Ditto specific operator." %} +**Example** -- filter Things that have a Feature with ID "feature_1": -**Example - filter things which have a feature with ID "feature_1"** -``` +```text exists(features/feature_1) ``` -**Example - filter lamps which are located in the "living-room"** -``` +**Example** -- filter lamps located in the "living-room": + +```text and(exists(features/lamp),eq(attributes/location,"living-room")) ``` @@ -311,79 +294,82 @@ or(empty(attributes/tags),eq(attributes/tags,"default")) ### Logical operators #### and -Ensure that all given queries match. -``` -and(,, ...) -``` +All given queries must match. -**Example - filter things which are located on the "upper floor" in the "living-room"** +```text +and(,, ...) ``` + +**Example** -- filter Things on the "upper floor" in the "living-room": + +```text and(eq(attributes/floor,"upper floor"),eq(attributes/location,"living-room")) ``` - + #### or -At least one of the given queries match. -``` -or(,, ...) -``` +At least one of the given queries must match. -**Example - filter all things located on the "upper floor", and all things with location "living-room"** +```text +or(,, ...) ``` + +**Example** -- filter Things on the "upper floor" or in the "living-room": + +```text or(eq(attributes/floor,"upper floor"),eq(attributes/location,"living-room")) ``` #### not -Negates the given query. -``` -not() -``` +Negate the given query. -**Example - filter things whose ID do not start with a common prefix** +```text +not() ``` + +**Example** -- filter Things whose ID does not start with a common prefix: + +```text not(like(thingId,"org.eclipse.ditto:blocked*")) ``` ## RQL sorting -The RQL sorting part specifies in which order the result should be returned. +The sorting part specifies result order: -``` +```text sort(<+|->,<+|->,...) ``` -* Use **+** for an ascending sort order (URL encoded character **%2B**) -* Use **-** for a descending sort order (URL encoded character **%2D**) +* Use **+** for ascending order (URL encoded: `%2B`) +* Use **-** for descending order (URL encoded: `%2D`) -**Example - sort the list ascending by the thing ID** -``` -sort(+thingId) -``` +**Examples**: -**Example - sort the list ascending by an attribute** -``` +```text +sort(+thingId) sort(+attributes/location) -``` - -**Example - multiple sort options** -``` sort(-attributes/location,+thingId) ``` -This expression will sort the list descending by location attribute.
    -In case there are multiple things with the same location attribute, these are sorted ascending by their ID. +The last example sorts descending by location, then ascending by Thing ID for ties. ### Sorting of string values -{% include note.html content="Sorting does not support a special \"semantics\" of string comparison (e.g. regarding - alphabetical or lexicographical ordering). However, you can rely on the alphabetical sorting of strings with the +{% include note.html content="Sorting does not support a special \"semantics\" of string comparison (e.g. regarding + alphabetical or lexicographical ordering). However, you can rely on the alphabetical sorting of strings with the same length (e.g. \"aaa\" < \"zzz\") and that the order stays the same over multiple/different filter requests." %} ### Sorting of other values -{% include note.html content="Sorting does not support a special \"semantics\" of comparison for fields with values of - different data types (e.g. numbers vs. strings). However, you can rely on the fact that values of the same type are +{% include note.html content="Sorting does not support a special \"semantics\" of comparison for fields with values of + different data types (e.g. numbers vs. strings). However, you can rely on the fact that values of the same type are sorted respectively." %} - + +## Further reading + +- [Search](basic-search.html) -- search concepts and paging +- [Conditional requests](basic-conditional-requests.html) -- using RQL as conditions for updates +- [Change notifications](basic-changenotifications.html) -- filtering notifications with RQL diff --git a/documentation/src/main/resources/pages/ditto/basic-search.md b/documentation/src/main/resources/pages/ditto/basic-search.md index 26078db69f2..d2793b543e8 100644 --- a/documentation/src/main/resources/pages/ditto/basic-search.md +++ b/documentation/src/main/resources/pages/ditto/basic-search.md @@ -5,93 +5,75 @@ tags: [search, rql] permalink: basic-search.html --- -Ditto provides a search functionality as one of the services around its managed **digital twins**. -The functionality is available for the following APIs. +Ditto provides a search service that lets you query across all managed digital twins using [RQL expressions](basic-rql.html). + +{% include callout.html content="**TL;DR**: You search Things using RQL filter expressions, with results sorted and paged. The search index updates asynchronously (eventual consistency) -- typically within 1-2 seconds." type="primary" %} + +## Overview + +You can access the search functionality through three APIs: | API | Access Method | Characteristics | -|-----|---------------|-----------------| -|[HTTP](httpapi-search.html)|HTTP request-response|Stateless| -|[Ditto protocol](protocol-specification-things-search.html)|[Websocket](httpapi-protocol-bindings-websocket.html) and [connections](basic-connections.html)| [Reactive-streams](https://reactive-streams.org) compatible | -|[Server-sent events](httpapi-sse.html#sse-api-searchthings)|[HTML5 server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html)|Streaming with resumption| +|---|---|---| +| [HTTP](httpapi-search.html) | HTTP request-response | Stateless | +| [Ditto protocol](protocol-specification-things-search.html) | [WebSocket](httpapi-protocol-bindings-websocket.html) and [connections](basic-connections.html) | [Reactive-streams](https://reactive-streams.org) compatible | +| [Server-sent events](httpapi-sse.html#sse-for-search-results) | [HTML5 server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html) | Streaming with resumption | -## Search index +## How it works -Ditto's microservice [things-search](architecture-services-things-search.html) automatically consumes all -[events](basic-signals-event.html) which are emitted for changes to `Things` and `Policies` and updates an for search -optimized representation of the `Thing` data into its own database. +### Search index -No custom indexes have to be defined as the structure in the database is "flattened" so that all data contained in -[Things](basic-thing.html) can be searched for efficiently. +Ditto's [things-search](architecture-services-things-search.html) microservice automatically consumes all [events](basic-signals-event.html) emitted for changes to `Things` and `Policies`. It updates a search-optimized representation in its own database. -## Consistency +You do not need to define custom indexes. The database structure is "flattened" so that all data in [Things](basic-thing.html) can be searched efficiently. -Ditto's search index provides **eventual consistency**. +### Consistency -In order to reduce load to the database when processing updates in a high frequency, the search index is updated -with a default interval of 1 second (configurable via environment variable `THINGS_SEARCH_UPDATER_STREAM_WRITE_INTERVAL`). +The search index provides **eventual consistency**. Updates are written at a default interval of 1 second (configurable via `THINGS_SEARCH_UPDATER_STREAM_WRITE_INTERVAL`). -That means that when a thing is updated and the API (e.g. the HTTP endpoint) returns a success response, the search index -will not reflect that change in that instant. The change will most likely be reflected in the search index within -1-2 seconds. In rare cases the duration until consistency is reached again might be higher. +When you update a Thing and receive a success response, the search index does not reflect that change immediately. The change typically appears within 1-2 seconds. -If it is important to know when a twin modification is reflected in the search index, request the -[built-in acknowledgement](basic-acknowledgements.html#built-in-acknowledgement-labels) `search-persisted` -in the corresponding command. -Search index update is successful if the status code of `search-persisted` in the command response is 204 "no content". -Status codes at or above 400 indicate failed search index update due to client or server errors. +If you need to know when a modification is reflected in the search index, request the [built-in acknowledgement](basic-acknowledgements.html#built-in-acknowledgement-labels) `search-persisted` in the command. A status code of `204` confirms successful index update. Status codes at or above `400` indicate failure. ## Search queries -Queries can be made via Ditto's APIs ([HTTP](httpapi-search.html) or -[Ditto Protocol](protocol-specification-things-search.html) e.g. via [WebSocket](httpapi-protocol-bindings-websocket.html)). +You formulate search queries using the Ditto-supported subset of [RQL](basic-rql.html). Queries work through the [HTTP API](httpapi-search.html) or the [Ditto Protocol](protocol-specification-things-search.html) (e.g., via [WebSocket](httpapi-protocol-bindings-websocket.html)). -Search queries are formulated using the by Ditto supported subset of [RQL](basic-rql.html). +### Querying scalar JSON values -### Search queries on scalar JSON values +The [query property](basic-rql.html#query-property) can target scalar JSON values: booleans, numbers, or strings. -The [query property](basic-rql.html#query-property) used in a search can contain either a scalar JSON value: -* JSON boolean -* JSON number -* JSON string +**Example** -- find all Things located in "living-room", sorted ascending by Thing ID, returning the first 5 results: -**Example:** Search for all things located in "living-room", reorder the list to start with the lowest thing ID as -the first element, and return the first 5 results: -``` +```text Filter: eq(attributes/location,"living-room") Sorting: sort(+thingId) Paging: size(5),cursor(CURSOR_ID) ``` -### Search queries in JSON arrays +### Querying JSON arrays -Or the [query property](basic-rql.html#query-property) used in a search it can also contain a JSON array. -The search index will index any values in that array, even arrays if mixed types are supported. +The query property can also target JSON arrays. Ditto indexes all values in the array, including mixed types. + +Given a Thing with tags of different types: -For example, assuming that we have the following thing containing special "tags" of different types: ```json { "thingId": "org.eclipse.ditto:tagged-thing-1", "policyId": "org.eclipse.ditto:tagged-thing-1", "attributes": { "tags": [ - "misc", - "no-due-date", - "high-priority", - 2, - 3, - 5, - false, - { - "room": "kitchen", - "floor": 2 - } + "misc", "no-due-date", "high-priority", + 2, 3, 5, false, + { "room": "kitchen", "floor": 2 } ] } } ``` -We can formulate various different queries on different scalar values: -``` +You can query against scalar values in the array: + +```text eq(attributes/tags,"high-priority") -> match: "high-priority" is contained @@ -111,97 +93,94 @@ gt(attributes/tags,6) -> no match: as no number > 6 is contained ``` -And we can even formulate queries on JSON objects contained in the JSON array: -``` -exists(attributes/tags/room) --> match: array contains one object having a key "room" +You can also query JSON objects inside the array: -eq(attributes/tags/room,"kitchen") --> match: array contains one object with "room"="kitchen" - -ge(attributes/tags/floor,2) --> match: array contains one object where floor is >= 2 +```text +exists(attributes/tags/room) -> match +eq(attributes/tags/room,"kitchen") -> match +ge(attributes/tags/floor,2) -> match ``` - ## Search count queries -The same syntax applies for search count queries - only the [sorting](basic-rql.html#rql-sorting) and -[paging](#rql-paging-deprecated) makes no sense here, so there are not necessary to specify. - +Count queries use the same filter syntax. Sorting and paging options are not applicable for count queries. ## Namespaces -The Search supports specifying in which `namespaces` it should be searched. This may significantly improve the search -performance when many Things of different namespaces are managed in Ditto's search index. - +You can restrict the search to specific `namespaces`. This can significantly improve performance when many Things from different namespaces exist in the index. ## RQL -In order to apply queries when searching, Ditto uses the [RQL notation](basic-rql.html) which is also applied for other -scenarios (e.g. filtering [notifications](basic-changenotifications.html)). - +Ditto uses [RQL notation](basic-rql.html) for search queries and other scenarios such as filtering [notifications](basic-changenotifications.html). ## Sorting and paging options -The [`sort` option](basic-rql.html#rql-sorting) governs the order of search results. +### Sorting -``` +The [`sort` option](basic-rql.html#rql-sorting) controls the order of search results: + +```text sort(<+|->,<+|->,...) ``` -If not given, search results are listed in the ascending order of thing IDs, namely `sort(+thingId)`. +If not specified, results are sorted ascending by Thing ID: `sort(+thingId)`. -The `size` option -``` +### Paging with cursor + +The `size` option limits results per response: + +```text size() ``` -limits the search results delivered in one HTTP response or one Ditto protocol message to `` items. -If the paging option is not explicitly specified a **default value** of _25_ is used. -The **maximum** allowed count is _200_. +Default: **25**. Maximum: **200**. -``` +Use cursor-based paging to iterate through large result sets: + +```text cursor() ``` -Starts the search at the position of the cursor with ID ``. The cursor ID is obtained from the field -`cursor` of a previous response and marks the **position after the last entry** of the previous search. A response -includes no cursor if there are no more results. -If a request has a `cursor` option, then any included `filter` or `sort` option may not differ from the original request -of the cursor. Otherwise, the request is rejected. +The cursor ID comes from the `cursor` field of a previous response and marks the position after the last returned entry. No cursor in the response means no more results. -**Example - return ten items with a cursor** -``` +If a request includes a `cursor`, the `filter` and `sort` options must match the original request that produced the cursor. + +**Example** -- return ten items with a cursor: + +```text option=size(10),cursor() ``` ## RQL paging (deprecated) -{% include note.html content="The limit option is deprecated, it may be removed in future releases. Use [cursor-based +{% include note.html content="The limit option is deprecated, it may be removed in future releases. Use [cursor-based paging](basic-search.html#sorting-and-paging-options) instead." %} -The RQL limiting part specifies which part (or "page") should be returned of a large search result set. +The `limit` option specifies which page to return: -``` +```text limit(,) ``` -Limits the search results to `` items, starting with the item at index ``. -* if the paging option is not explicitly specified, the **default** value `limit(0,25)` is used, - i.e. the first `25` results are returned. -* the **maximum** allowed count is `200`. +Default: `limit(0,25)`. Maximum count: `200`. -**Example - return the first ten items** -``` +**Example** -- return the first ten items: + +```text limit(0,10) ``` -**Example - return the items 11 to 20** -``` +**Example** -- return items 11 to 20: + +```text limit(10,10) ``` -i.e. Return the next ten items (from index 11 to 20) {% include note.html content="We recommend **not to use high offsets** (e.g. higher than 10000) for paging because of potential performance degradations." %} + +## Further reading + +- [RQL expressions](basic-rql.html) -- query language reference +- [Search protocol](protocol-specification-things-search.html) -- reactive-streams search via Ditto Protocol +- [HTTP search API](httpapi-search.html) -- REST-based search diff --git a/documentation/src/main/resources/pages/ditto/basic-signals-announcement.md b/documentation/src/main/resources/pages/ditto/basic-signals-announcement.md index d79ef0a6970..607d1b4d56e 100644 --- a/documentation/src/main/resources/pages/ditto/basic-signals-announcement.md +++ b/documentation/src/main/resources/pages/ditto/basic-signals-announcement.md @@ -5,11 +5,4 @@ tags: [signal] permalink: basic-signals-announcement.html --- -Announcements are special signals which are published in order to announce something before it actually happens. -For example, before an [event](basic-signals-event.html) is created and published, an announcement could signal that -the event will happen soon. - -Announcements have the following characteristics: -* they are **not** persisted/appended into any data store -* they are published to interested and authorized parties via the [WebSocket API](httpapi-protocol-bindings-websocket.html) - as well as [connection targets](basic-connections.html#targets) via [change notifications](basic-changenotifications.html). +{% include callout.html content="This content has been merged into the [Signals & Communication Pattern](basic-signals.html#announcements) page. Please refer to that page for full details on announcements." type="primary" %} diff --git a/documentation/src/main/resources/pages/ditto/basic-signals-command.md b/documentation/src/main/resources/pages/ditto/basic-signals-command.md index 435e9e424ef..d62688442fd 100644 --- a/documentation/src/main/resources/pages/ditto/basic-signals-command.md +++ b/documentation/src/main/resources/pages/ditto/basic-signals-command.md @@ -5,27 +5,4 @@ tags: [signal] permalink: basic-signals-command.html --- -Commands involve the need to change or retrieve something of a **digital twin** managed by Ditto or an actual device -connected to Ditto. - -Commands always contain an identifier of the entity they address (e.g. a `Thing ID`). - -## Modify Commands - -Commands which modify a **digital twin** or an actual device are grouped as "Modify Commands".
    -In CQRS system those are simply -referred to as *commands*. - -An overview of all Thing related modify commands can be found in the appropriate chapter of the Ditto Protocol: -* [Create/Modify protocol specification](protocol-specification-things-create-or-modify.html), -* [Merge protocol specification](protocol-specification-things-merge.html), -* [Delete protocol specification.](protocol-specification-things-delete.html) - -## Query Commands - -Commands which only retrieve information about a **digital twin** or an actual device are grouped as "Query Commands".
    -In CQRS system those are simply -referred to as *queries*. - -An overview of all Thing related query commands can be found in the chapter -["Retrieve protocol specification"](protocol-specification-things-retrieve.html) of the Ditto Protocol. +{% include callout.html content="This content has been merged into the [Signals & Communication Pattern](basic-signals.html#commands) page. Please refer to that page for full details on commands, including modify commands and query commands." type="primary" %} diff --git a/documentation/src/main/resources/pages/ditto/basic-signals-commandresponse.md b/documentation/src/main/resources/pages/ditto/basic-signals-commandresponse.md index 1f72bf2a9d0..0c22e7558f1 100644 --- a/documentation/src/main/resources/pages/ditto/basic-signals-commandresponse.md +++ b/documentation/src/main/resources/pages/ditto/basic-signals-commandresponse.md @@ -5,8 +5,4 @@ tags: [signal] permalink: basic-signals-commandresponse.html --- -CommandResponses are the answer to [Commands](basic-signals-command.html) and include information about whether the -intention of changing something via a `ModifyCommand` has worked or if there was an [Error](basic-signals-errorresponse.html) -instead. - -The CommandResponse of QueryCommands contains the requested information. +{% include callout.html content="This content has been merged into the [Signals & Communication Pattern](basic-signals.html#command-responses) page. Please refer to that page for full details on command responses." type="primary" %} diff --git a/documentation/src/main/resources/pages/ditto/basic-signals-errorresponse.md b/documentation/src/main/resources/pages/ditto/basic-signals-errorresponse.md index 3d87bbf15fc..4e829f57994 100644 --- a/documentation/src/main/resources/pages/ditto/basic-signals-errorresponse.md +++ b/documentation/src/main/resources/pages/ditto/basic-signals-errorresponse.md @@ -5,14 +5,4 @@ tags: [signal] permalink: basic-signals-errorresponse.html --- -If an issued [command](basic-signals-command.html) or [message](basic-messages.html) could not be applied, an -appropriate error response conveys this [error](basic-errors.html) information back to the issuer. -Failure of a command or message can have various reasons, starting from missing permissions to internal server errors -during processing of the command. - -The [Ditto Protocol for Errors](protocol-specification-errors.html) defines how error responses look in Ditto Protocol. - -An overview of some possible error responses can be found in the examples chapters: -* [Things error response examples](protocol-examples-errorresponses.html) -* [Policies error response examples](protocol-examples-policies-errorresponses.html) - +{% include callout.html content="This content has been merged into the [Signals & Communication Pattern](basic-signals.html#error-responses) page. Please refer to that page for full details on error responses." type="primary" %} diff --git a/documentation/src/main/resources/pages/ditto/basic-signals-event.md b/documentation/src/main/resources/pages/ditto/basic-signals-event.md index 9741528f3a1..75feaad6971 100644 --- a/documentation/src/main/resources/pages/ditto/basic-signals-event.md +++ b/documentation/src/main/resources/pages/ditto/basic-signals-event.md @@ -5,13 +5,4 @@ tags: [signal] permalink: basic-signals-event.html --- -Events report that something took place in a **digital twin** in Ditto. Important is the "past tense" here; it took -already place (it was for example persisted into the data store) and cannot be reversed or stopped. - -Events are one of the centerpieces of Ditto: -* they are persisted/appended into the data store, -* they are published in the Ditto cluster, so other Ditto back end services can react on them (e.g. in order to update - the search index) and -* they are published to interested and authorized parties via the [WebSocket API](httpapi-protocol-bindings-websocket.html) as - well as via [HTTP Server Sent Events](httpapi-sse.html) as well as [connection targets](basic-connections.html#targets) - via [change notifications](basic-changenotifications.html). +{% include callout.html content="This content has been merged into the [Signals & Communication Pattern](basic-signals.html#events) page. Please refer to that page for full details on events." type="primary" %} diff --git a/documentation/src/main/resources/pages/ditto/basic-signals.md b/documentation/src/main/resources/pages/ditto/basic-signals.md index 5ff6a19fcc8..daf870babb4 100644 --- a/documentation/src/main/resources/pages/ditto/basic-signals.md +++ b/documentation/src/main/resources/pages/ditto/basic-signals.md @@ -1,59 +1,148 @@ --- -title: Signals -keywords: command, communication, CQRS, DDD, event, EventSourcing, response, signal +title: Signals & Communication Pattern +keywords: command, communication, CQRS, DDD, event, EventSourcing, response, signal, announcement tags: [signal] permalink: basic-signals.html --- -Ditto has a concept called `Signal` which combines common functionality of -* [Commands](basic-signals-command.html), -* [Command Responses](basic-signals-commandresponse.html), -* [Error Responses](basic-signals-errorresponse.html), -* [Events](basic-signals-event.html) and -* [Announcements](basic-signals-announcement.html). +Signals are the messages that flow through Ditto. Every interaction -- creating a Thing, querying +a property, receiving a change notification -- is carried by a signal. -Such common functionality is for example that all those have header fields in which they can be for example correlated -to each other. +{% include callout.html content="**TL;DR**: Ditto uses five signal types: Commands (requests to read or change data), +Command Responses (success replies), Error Responses (failure replies), Events (records of changes +that already happened), and Announcements (advance notices of upcoming changes)." type="primary" %} -Signals are one of the core concepts of Ditto but they mostly are used internally for communication in the Ditto -cluster. -Nevertheless it is very helpful to have a basic understanding of what the Signal types are and in which communication -pattern they occur. +## Overview +Ditto follows the CQRS and +Event Sourcing architectural patterns. +[This page](https://cqrs.nu/Faq) provides a good explanation of the basic concepts: -## Architectural style - -Ditto uses Commands, Events, -CQRS and EventSourcing. -[This page](https://cqrs.nu/Faq) provides a quite good explanation of the basic concepts on all of those aspects: - -### Command - -> People request changes to the domain by sending commands. -They are named with a verb in the imperative mood plus and may include the aggregate type, for example `ConfirmOrder`. -Unlike an event, a command is not a statement of fact; it's only a request, and thus may be refused. -(A typical way to convey refusal is to throw an exception). - -### Event +> People request changes to the domain by sending commands. +They are named with a verb in the imperative mood plus and may include the aggregate type, for example +`ConfirmOrder`. Unlike an event, a command is not a statement of fact; it's only a request, and thus may be refused. > An event represents something that took place in the domain. -They are always named with a past-participle verb, such as `OrderConfirmed`. -It's not unusual but also not required for an event to name an aggregate or entity that it relates to; let the domain -language be your guide.
    +They are always named with a past-participle verb, such as `OrderConfirmed`. Since an event represents something in the past, it can be considered a statement of fact and used to take decisions in other parts of the system. +Every change goes through a well-defined flow: -## Communication pattern - -1. A **command** is sent to Ditto where it is then processed. -2. Either a **success response** or an **error response** is sent back to the issuer of the **command**. -3. In addition an **event** is both persisted into the datastore and published.
    - The event describes that the change was applied to an entity (e.g. a `Thing`).
    - Interested parties can subscribe for such **events** and follow the evolving entity. +1. A **Command** arrives requesting a change or a query. +2. Ditto processes it and sends back either a **Command Response** (success) or an **Error Response** + (failure). +3. If the command modified data, Ditto persists an **Event** and publishes it to subscribers. {% include note.html - content="Events caused by commands from a **[connection](basic-connections.html)** or a - [websocket session](httpapi-protocol-bindings-websocket.html) are not published - **to the same origin**. The connection can receive a response, but will not additionally get an event." + content="Events caused by commands from a **[connection](basic-connections.html)** or a + [WebSocket session](httpapi-protocol-bindings-websocket.html) are not published + **to the same origin**. The connection receives the response, but not the event." %} + +All signal types share common header fields (like correlation IDs) that let you trace a request +through its entire lifecycle. + +## Commands + +Commands are requests to change or retrieve data from a digital twin or a connected device. Every +command targets a specific entity by its ID (for example, a Thing ID). + +See [Commands detail](basic-signals-command.html) for the full command lifecycle. + +### Modify commands + +Modify commands change the state of a digital twin or trigger an action on a device. In CQRS +terminology, these are the "commands" (write side). + +Related protocol specifications: +* [Create/Modify protocol specification](protocol-specification-things-create-or-modify.html) +* [Merge protocol specification](protocol-specification-things-merge.html) +* [Delete protocol specification](protocol-specification-things-delete.html) + +### Query commands + +Query commands retrieve data without changing anything. In CQRS terminology, these are the +"queries" (read side). + +Related protocol specification: +* [Retrieve protocol specification](protocol-specification-things-retrieve.html) + +## Command Responses + +A command response is the reply to a command. It tells you whether the operation succeeded: + +* For **modify commands**: the response confirms the change was applied (for example, `201 Created` + or `204 No Content`). +* For **query commands**: the response contains the requested data. + +If something goes wrong, you receive an [error response](#error-responses) instead. + +See [Command Responses detail](basic-signals-commandresponse.html) for response structure and examples. + +## Error Responses + +When a command or [message](basic-messages.html) fails, Ditto sends an error response explaining +what went wrong. Failures can happen for many reasons: + +* Missing permissions +* Entity not found +* Invalid input data +* Internal server errors + +The [Ditto Protocol for Errors](protocol-specification-errors.html) defines the error response format. +See also [Error Responses detail](basic-signals-errorresponse.html). + +Example error responses: +* [Things error responses](protocol-examples-errorresponses.html) +* [Policies error responses](protocol-examples-policies-errorresponses.html) + +## Events + +Events record that something **already happened**. They are past tense and irreversible -- the +change is already persisted. + +See [Events detail](basic-signals-event.html) for event structure and persistence. + +Events serve three purposes in Ditto: + +1. **Persistence** -- appended to the event journal (event sourcing) as the source of truth +2. **Internal coordination** -- published within the Ditto cluster so services can react (for + example, the search index updates itself based on events) +3. **External notification** -- delivered to authorized subscribers via the + [WebSocket API](httpapi-protocol-bindings-websocket.html), + [HTTP Server Sent Events](httpapi-sse.html), and + [connection targets](basic-connections.html#targets) as + [change notifications](basic-changenotifications.html) + +## Announcements + +Announcements signal that something **is about to happen**. Unlike events, announcements are +forward-looking and are **not** persisted. + +For example, before a [Policy subject expires](basic-policy.html#expiring-subjects), Ditto can +publish an announcement so your application can react -- perhaps by renewing the subject's +credentials. + +Announcements are published to authorized subscribers via the +[WebSocket API](httpapi-protocol-bindings-websocket.html) and +[connection targets](basic-connections.html#targets). + +See [Announcements detail](basic-signals-announcement.html) for announcement types and examples. + +## Communication pattern summary + +| Step | Signal type | Direction | Persisted? | +|------|-------------|-----------|------------| +| 1 | Command | Client to Ditto | No | +| 2a | Command Response | Ditto to client | No | +| 2b | Error Response | Ditto to client (on failure) | No | +| 3 | Event | Ditto to subscribers | Yes | +| -- | Announcement | Ditto to subscribers (preemptive) | No | + +## Further reading + +* [Change Notifications](basic-changenotifications.html) -- subscribe to events via different APIs +* [Signal Enrichment](basic-enrichment.html) -- add extra context to events +* [Messages](basic-messages.html) -- send arbitrary payloads to/from devices +* [Ditto Protocol specification](protocol-specification.html) -- wire format for all signal types diff --git a/documentation/src/main/resources/pages/ditto/basic-thing.md b/documentation/src/main/resources/pages/ditto/basic-thing.md index af3e28c96df..b030f196800 100644 --- a/documentation/src/main/resources/pages/ditto/basic-thing.md +++ b/documentation/src/main/resources/pages/ditto/basic-thing.md @@ -1,77 +1,85 @@ --- -title: Thing +title: Things keywords: entity, feature, model, namespace, thing tags: [model] permalink: basic-thing.html --- -The versatile assets in IoT applications can be managed as Things. +A Thing is Ditto's core entity. It represents any asset you want to manage as a digital twin -- a +physical device, a virtual grouping, or any concept you can model as structured data. +{% include callout.html content="**TL;DR**: A Thing is a JSON object with an ID, a Policy reference, optional +attributes (static metadata), and optional features (dynamic state data)." type="primary" %} -## Thing +## What is a Thing? -Things are very generic entities and are mostly used as a “handle” for multiple features belonging to this Thing. +A Thing is a generic container that acts as a handle for all the data related to one asset. You +decide what a Thing represents: -Examples: - -* Physical Device: a lawn mower, a sensor, a vehicle, a lamp. -* Virtual Device: a room in a house, a virtual power plant spanning multiple power plants, the weather information for - a specific location collected by various sensors. -* Transactional entity: a tour of a vehicle (from start until stop), a series of measurements of a machine. -* Master data entity: a supplier of devices or a service provider for devices, an entity representing a city/region. -* Anything else - if it can be modeled and managed appropriately by the supported concepts/capabilities. +* **Physical device** -- a sensor, a lamp, a vehicle, a lawn mower +* **Virtual device** -- a room in a building, a virtual power plant, weather data for a location +* **Transactional entity** -- a vehicle trip from start to stop, a series of machine measurements +* **Master data entity** -- a device supplier, a service provider, a geographic region +* **Anything else** that fits the [data model](basic-overview.html) +## Thing structure ### Thing ID -Unique identifier of a Thing. For choosing custom Thing IDs when creating a Thing, the rules for -[namespaced IDs](basic-namespaces-and-names.html#namespaced-id) apply. - -### Access control +Every Thing has a unique identifier that follows the [namespaced ID](basic-namespaces-and-names.html#namespaced-id) +format: `:`. For example: `com.example:temperature-sensor-42`. -A Thing in API version 2 contains a link to a [Policy](basic-policy.html) in form of a `policyId`. -This Policy defines which -authenticated subjects may READ and WRITE the Thing or even parts of it (hierarchically specified). +### Policy reference +Each Thing links to a [Policy](basic-policy.html) via its `policyId`. The +Policy defines which +authenticated subjects may read and write the Thing -- down to individual attributes and feature +properties. ### Definition -A Thing may contain a definition. The definition can also be used to find Things. The definition is used to link a thing -to a corresponding model defining the capabilities/features of it. -The definition can for example point to a: -* a valid HTTP(s) URL (e.g. in order to define that the Thing is described by a [WoT (Web of Things) Thing Model](basic-wot-integration.html#thing-model-describing-a-ditto-thing)) -* or some other model reference using the syntax `::` +A Thing can include a `definition` that links it to a model describing its capabilities. The +definition can be: +* A valid HTTP(s) URL -- for example pointing to a [WoT (Web of Things) Thing Model](basic-wot-integration.html#thing-model-describing-a-ditto-thing) +* A reference using the syntax `::` -- for example `org.eclipse.ditto:HeatingDevice:2.1.0` ### Attributes -Attributes describe the Thing in more detail and can be of any type. Attributes can also be used to find Things. -Attributes are typically used to model rather static properties at the Thing level. Static means that the values do not -change as frequently as property values of Features. +Attributes store static metadata about the Thing as a JSON object. Use them for data that does +not change frequently -- manufacturer, serial number, installation location. You can nest values +to any depth: +```json +{ + "manufacturer": "ACME corp", + "location": { + "building": "HQ", + "floor": 3, + "room": "Lab-A" + } +} +``` ### Features -A Thing may contain an arbitrary amount of [Features](basic-feature.html). +A Thing can contain any number of [Features](basic-feature.html). Each Feature groups related +dynamic state data (sensor readings, configuration, operational status) under a named identifier. {% include image.html file="pages/basic/ditto-thing-feature.png" alt="Thing and Feature" caption="One Thing can have many Features" max-width=100 %} - ### Metadata -A Thing may contain additional [metadata](basic-metadata.html) for all of its attributes and features describing the -semantics of the data or adding other useful information about the data points of the twin. - +A Thing can carry additional [metadata](basic-metadata.html) attached to any of its attributes or +feature properties -- for example, timestamps recording when values last changed or units of +measurement. -### Model specification - -#### API version 2 +## Model specification {% include docson.html schema="jsonschema/thing_v2.json" %} - -### Example +## Example ```json { @@ -79,29 +87,36 @@ semantics of the data or adding other useful information about the data points o "policyId": "the.namespace:thePolicyName", "definition": "org.eclipse.ditto:HeatingDevice:2.1.0", "attributes": { - "someAttr": 32, - "manufacturer": "ACME corp" + "someAttr": 32, + "manufacturer": "ACME corp" }, "features": { - "heating-no1": { - "properties": { - "connected": true, - "complexProperty": { - "street": "my street", - "house no": 42 - } - }, - "desiredProperties": { - "connected": false - } + "heating-no1": { + "properties": { + "connected": true, + "complexProperty": { + "street": "my street", + "house no": 42 + } }, - "switchable": { - "definition": [ "org.eclipse.ditto:Switcher:1.0.0" ], - "properties": { - "on": true, - "lastToggled": "2017-11-15T18:21Z" - } + "desiredProperties": { + "connected": false } + }, + "switchable": { + "definition": [ "org.eclipse.ditto:Switcher:1.0.0" ], + "properties": { + "on": true, + "lastToggled": "2017-11-15T18:21Z" + } + } } } ``` + +## Further reading + +* [Features](basic-feature.html) -- properties, desired properties, and definitions +* [Policies](basic-policy.html) -- access control for Things +* [Namespaces & Names](basic-namespaces-and-names.html) -- ID format and naming rules +* [Metadata](basic-metadata.html) -- attach contextual information to Thing data diff --git a/documentation/src/main/resources/pages/ditto/basic-wot-integration-example.md b/documentation/src/main/resources/pages/ditto/basic-wot-integration-example.md index f41e0e5c8eb..8131ff72a65 100644 --- a/documentation/src/main/resources/pages/ditto/basic-wot-integration-example.md +++ b/documentation/src/main/resources/pages/ditto/basic-wot-integration-example.md @@ -1,63 +1,60 @@ --- -title: WoT integration example +title: WoT Integration Example keywords: WoT, TD, TM, ThingDescription, ThingModel, W3C, Semantic, Model, definition, ThingDefinition, FeatureDefinition, example tags: [wot] permalink: basic-wot-integration-example.html --- -Wrapping up the [WoT integration](basic-wot-integration.html) with a practical example. +This page walks you through a complete WoT integration example -- from creating a Thing with a WoT Thing Model to inspecting the generated Thing Descriptions for both the Thing and its Features. + +{% include callout.html content="**TL;DR**: Create a Thing with a `definition` pointing to a WoT Thing Model URL. Ditto generates the full JSON skeleton automatically. Then request `Accept: application/td+json` to get a complete Thing Description with API endpoints." type="primary" %} {% include tip.html content="To experiment with Thing Models and having them exposed as HTTP resources, simply create them as a [GitHub Gist](https://gist.github.com).
    Each revision of the file will get a unique HTTP endpoint which you can use as endpoint for your Thing Model." %} -## Thing Model - -You can provide a WoT Thing Model via any HTTP(s) URL addressable endpoint, for example simply put your WoT TMs into -a GitHub repository. -For this example, Ditto added a model into its `ditto-examples` GitHub Repo: -[floor-lamp-1.0.0.tm.jsonld](https://github.com/eclipse-ditto/ditto-examples/blob/master/wot/models/floor-lamp-1.0.0.tm.jsonld) - -This file is available as HTTP served file at: -[https://eclipse-ditto.github.io/ditto-examples/wot/models/floor-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/floor-lamp-1.0.0.tm.jsonld) - -The example model is composed of the following submodels: -* [dimmable-colored-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/dimmable-colored-lamp-1.0.0.tm.jsonld), instanceName: "Spot1" - * which `tm:extends` [colored-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/colored-lamp-1.0.0.tm.jsonld) - * which `tm:extends` [switchable-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/switchable-1.0.0.tm.jsonld) -* [dimmable-colored-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/dimmable-colored-lamp-1.0.0.tm.jsonld), instanceName: "Spot2" - * which `tm:extends` [colored-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/colored-lamp-1.0.0.tm.jsonld) - * which `tm:extends` [switchable-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/switchable-1.0.0.tm.jsonld) -* [dimmable-colored-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/dimmable-colored-lamp-1.0.0.tm.jsonld), instanceName: "Spot3" - * which `tm:extends` [colored-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/colored-lamp-1.0.0.tm.jsonld) - * which `tm:extends` [switchable-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/switchable-1.0.0.tm.jsonld) -* [connection-status-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/connection-status-1.0.0.tm.jsonld), instanceName: "ConnectionStatus" -* [power-consumption-aware-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/power-consumption-aware-1.0.0.tm.jsonld), instanceName: "PowerConsumptionAwareness" -* [smoke-detector-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/smoke-detector-1.0.0.tm.jsonld), instanceName: "SmokeDetection" -* [colored-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/colored-lamp-1.0.0.tm.jsonld), instanceName: "Status-LED" - * which `tm:extends` [switchable-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/switchable-1.0.0.tm.jsonld) - -Summarizing, our example model is the model of a "Floor Lamp" with: -* 3 (dimmable, colored, switchable) Spots -* a connection status indicating whether the lamp is currently connected -* awareness of its current power consumption -* an included smoke detector -* and a (colored, switchable) status LED - - -## Creating a new Thing based on the TM - -To create a Thing (instance) and create the Thing JSON skeleton following the WoT Thing Model, simply create -a Thing via the Ditto HTTP API (e.g. `PUT /api/2/things/`): +## Overview + +This example uses a "Floor Lamp" model that demonstrates WoT composition with multiple sub-models. You will: + +1. Understand the Thing Model structure +2. Create a Thing that references the model +3. Inspect the generated Thing Description for the Thing +4. Inspect the generated Thing Description for a Feature + +## Step 1: Understand the Thing Model + +The example uses a floor lamp model hosted in the Ditto examples repository: +[floor-lamp-1.0.0.tm.jsonld](https://eclipse-ditto.github.io/ditto-examples/wot/models/floor-lamp-1.0.0.tm.jsonld) + +Source: [GitHub](https://github.com/eclipse-ditto/ditto-examples/blob/master/wot/models/floor-lamp-1.0.0.tm.jsonld) + +This model composes several sub-models: + +| Sub-model | Instance name | Inherits from | +|-----------|---------------|---------------| +| [dimmable-colored-lamp](https://eclipse-ditto.github.io/ditto-examples/wot/models/dimmable-colored-lamp-1.0.0.tm.jsonld) | Spot1, Spot2, Spot3 | colored-lamp -> switchable | +| [connection-status](https://eclipse-ditto.github.io/ditto-examples/wot/models/connection-status-1.0.0.tm.jsonld) | ConnectionStatus | -- | +| [power-consumption-aware](https://eclipse-ditto.github.io/ditto-examples/wot/models/power-consumption-aware-1.0.0.tm.jsonld) | PowerConsumptionAwareness | -- | +| [smoke-detector](https://eclipse-ditto.github.io/ditto-examples/wot/models/smoke-detector-1.0.0.tm.jsonld) | SmokeDetection | -- | +| [colored-lamp](https://eclipse-ditto.github.io/ditto-examples/wot/models/colored-lamp-1.0.0.tm.jsonld) | Status-LED | switchable | + +The floor lamp has 3 dimmable colored spots, a connection status indicator, power consumption awareness, a smoke detector, and a status LED. + +## Step 2: Create a Thing from the model + +Send a `PUT` request with just the `definition` field pointing to the Thing Model URL. Ditto generates the entire JSON skeleton for you: ```bash -curl --location --request PUT -u ditto:ditto 'https://ditto.eclipseprojects.io/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815' \ ---header 'Content-Type: application/json' \ ---data-raw '{ +curl --location --request PUT -u ditto:ditto \ + 'https://ditto.eclipseprojects.io/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815' \ + --header 'Content-Type: application/json' \ + --data-raw '{ "definition": "https://eclipse-ditto.github.io/ditto-examples/wot/models/floor-lamp-1.0.0.tm.jsonld" -}' + }' ``` -That should result in an HTTP status code `201` (Created) and return the following body: +Ditto returns `201 Created` with the generated Thing: + ```json { "thingId": "io.eclipseprojects.ditto:floor-lamp-0815", @@ -76,11 +73,7 @@ That should result in an HTTP status code `201` (Created) and return the followi ], "properties": { "dimmer-level": 0.0, - "color": { - "r": 0, - "g": 0, - "b": 0 - }, + "color": { "r": 0, "g": 0, "b": 0 }, "on": false } }, @@ -92,11 +85,7 @@ That should result in an HTTP status code `201` (Created) and return the followi ], "properties": { "dimmer-level": 0.0, - "color": { - "r": 0, - "g": 0, - "b": 0 - }, + "color": { "r": 0, "g": 0, "b": 0 }, "on": false } }, @@ -108,11 +97,7 @@ That should result in an HTTP status code `201` (Created) and return the followi ], "properties": { "dimmer-level": 0.0, - "color": { - "r": 0, - "g": 0, - "b": 0 - }, + "color": { "r": 0, "g": 0, "b": 0 }, "on": false } }, @@ -144,11 +129,7 @@ That should result in an HTTP status code `201` (Created) and return the followi "https://eclipse-ditto.github.io/ditto-examples/wot/models/switchable-1.0.0.tm.jsonld" ], "properties": { - "color": { - "r": 0, - "g": 0, - "b": 0 - }, + "color": { "r": 0, "g": 0, "b": 0 }, "on": false } } @@ -156,23 +137,24 @@ That should result in an HTTP status code `201` (Created) and return the followi } ``` -You see that: -* all of the `properties` defined in the `floor-lamp` itself were generated (as `attributes` in the Thing) with default values -* all included `tm:submodel`s were generated as Features -* all submodel `properties` were generated (as `properties` in the Feature) with default values -* Feature `definition`s were set accordingly, including the extension hierarchy - +Notice what Ditto generated automatically: +* **Attributes** from the TM's top-level `properties` (with empty string defaults) +* **Features** from each `tm:submodel` (using `instanceName` as the Feature ID) +* **Feature properties** from each submodel's `properties` (with default or neutral values) +* **Feature definitions** including the full extension hierarchy -## Inspecting the Thing Description of the Thing +## Step 3: Inspect the Thing Description -The Thing we just created can now be asked for its capabilities / interaction affordances by sending the following request: +Request the Thing Description using the `Accept: application/td+json` header: ```bash -curl --location --request GET -u ditto:ditto 'https://ditto.eclipseprojects.io/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815' \ ---header 'Accept: application/td+json' +curl --location --request GET -u ditto:ditto \ + 'https://ditto.eclipseprojects.io/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815' \ + --header 'Accept: application/td+json' ``` That should result in an HTTP status code `200` (OK) and return the following body: + ```json { "@context": [ @@ -518,24 +500,27 @@ That should result in an HTTP status code `200` (OK) and return the following bo } ``` -You see that: -* a complete and valid WoT Thing Description was generated and returned -* you can inspect which `properties`, `actions` and `events` the Thing level TD supports -* in the `forms` section you have concrete API endpoints (defined relatively to the top-level `base`) which describe how to e.g. read a single property, or to observe properties, or how to invoke actions -* you see in the `links` section that this TD contains other `"item"` relations with relative `"href"` link to the Thing's Features -* you get also possible `uriVariables` and a `dittoError` `schemaDefinition` which may be returned when accessing a `property` or invoking an `action` +Key observations: +* A complete and valid WoT Thing Description was generated and returned +* The `properties` section describes each attribute with its data type, read/write capabilities, and API forms +* The `actions` section defines invocable operations with their HTTP endpoints +* The `forms` section at root level provides bulk operations (read/write all properties, observe, subscribe) +* The `links` section contains `"item"` relations pointing to each Feature's TD (relative to the `base` URL) +* The `uriVariables` define query parameters (`channel`, `timeout`, `response-required`, `fields`) +* The `schemaDefinitions` provide a `dittoError` schema for error responses +## Step 4: Inspect a Feature's Thing Description -## Inspecting the Thing Description of a Feature - -In order to inspect which capabilities / interaction affordances now a Feature provides, simply perform such a query: +Request the TD for a specific Feature: ```bash -curl --location --request GET -u ditto:ditto 'https://ditto.eclipseprojects.io/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815/features/Spot1' \ ---header 'Accept: application/td+json' +curl --location --request GET -u ditto:ditto \ + 'https://ditto.eclipseprojects.io/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815/features/Spot1' \ + --header 'Accept: application/td+json' ``` That should result in an HTTP status code `200` (OK) and return the following body: + ```json { "@context": [ @@ -578,80 +563,44 @@ That should result in an HTTP status code `200` (OK) and return the following bo "href": "/properties{?channel,timeout}", "htv:methodName": "GET", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "readmultipleproperties", "href": "/properties{?fields,channel,timeout}", "htv:methodName": "GET", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "writeallproperties", "href": "/properties{?channel,timeout,response-required}", "htv:methodName": "PUT", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "writemultipleproperties", "href": "/properties{?channel,timeout,response-required}", "htv:methodName": "PATCH", "contentType": "application/merge-patch+json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { - "op": [ - "observeallproperties", - "unobserveallproperties" - ], + "op": ["observeallproperties", "unobserveallproperties"], "href": "/properties", "htv:methodName": "GET", "subprotocol": "sse", "contentType": "text/event-stream", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { - "op": [ - "subscribeallevents", - "unsubscribeallevents" - ], + "op": ["subscribeallevents", "unsubscribeallevents"], "href": "/outbox/messages", "htv:methodName": "GET", "subprotocol": "sse", "contentType": "text/event-stream", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] } ], "properties": { @@ -670,52 +619,29 @@ That should result in an HTTP status code `200` (OK) and return the following bo "href": "/properties/dimmer-level{?channel,timeout}", "htv:methodName": "GET", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "writeproperty", "href": "/properties/dimmer-level{?channel,timeout,response-required}", "htv:methodName": "PUT", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "writeproperty", "href": "/properties/dimmer-level{?channel,timeout,response-required}", "htv:methodName": "PATCH", "contentType": "application/merge-patch+json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { - "op": [ - "observeproperty", - "unobserveproperty" - ], + "op": ["observeproperty", "unobserveproperty"], "href": "/properties/dimmer-level", "htv:methodName": "GET", "subprotocol": "sse", "contentType": "text/event-stream", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] } ] }, @@ -724,30 +650,11 @@ That should result in an HTTP status code `200` (OK) and return the following bo "description": "The current color.", "type": "object", "properties": { - "r": { - "title": "Red", - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "g": { - "title": "Green", - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "b": { - "title": "Blue", - "type": "integer", - "minimum": 0, - "maximum": 255 - } + "r": { "title": "Red", "type": "integer", "minimum": 0, "maximum": 255 }, + "g": { "title": "Green", "type": "integer", "minimum": 0, "maximum": 255 }, + "b": { "title": "Blue", "type": "integer", "minimum": 0, "maximum": 255 } }, - "required": [ - "r", - "g", - "b" - ], + "required": ["r", "g", "b"], "observable": true, "forms": [ { @@ -755,52 +662,29 @@ That should result in an HTTP status code `200` (OK) and return the following bo "href": "/properties/color{?channel,timeout}", "htv:methodName": "GET", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "writeproperty", "href": "/properties/color{?channel,timeout,response-required}", "htv:methodName": "PUT", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "writeproperty", "href": "/properties/color{?channel,timeout,response-required}", "htv:methodName": "PATCH", "contentType": "application/merge-patch+json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { - "op": [ - "observeproperty", - "unobserveproperty" - ], + "op": ["observeproperty", "unobserveproperty"], "href": "/properties/color", "htv:methodName": "GET", "subprotocol": "sse", "contentType": "text/event-stream", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] } ] }, @@ -815,52 +699,29 @@ That should result in an HTTP status code `200` (OK) and return the following bo "href": "/properties/on{?channel,timeout}", "htv:methodName": "GET", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "writeproperty", "href": "/properties/on{?channel,timeout,response-required}", "htv:methodName": "PUT", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { "op": "writeproperty", "href": "/properties/on{?channel,timeout,response-required}", "htv:methodName": "PATCH", "contentType": "application/merge-patch+json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] }, { - "op": [ - "observeproperty", - "unobserveproperty" - ], + "op": ["observeproperty", "unobserveproperty"], "href": "/properties/on", "htv:methodName": "GET", "subprotocol": "sse", "contentType": "text/event-stream", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] } ] } @@ -879,12 +740,7 @@ That should result in an HTTP status code `200` (OK) and return the following bo "href": "/inbox/messages/toggle{?timeout,response-required}", "htv:methodName": "POST", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] } ] }, @@ -903,12 +759,7 @@ That should result in an HTTP status code `200` (OK) and return the following bo "href": "/inbox/messages/switch-on-for-duration{?timeout,response-required}", "htv:methodName": "POST", "contentType": "application/json", - "additionalResponses": [ - { - "success": false, - "schema": "dittoError" - } - ] + "additionalResponses": [{ "success": false, "schema": "dittoError" }] } ] } @@ -918,10 +769,7 @@ That should result in an HTTP status code `200` (OK) and return the following bo "type": "string", "title": "The Ditto channel to interact with.", "description": "Defines to which channel to route the command: 'twin' (digital twin) or 'live' (the device).", - "enum": [ - "twin", - "live" - ], + "enum": ["twin", "live"], "default": "twin" }, "timeout": { @@ -979,12 +827,34 @@ That should result in an HTTP status code `200` (OK) and return the following bo "maximum": 599 } }, - "required": [ - "status", - "error", - "message" - ] + "required": ["status", "error", "message"] } } } -``` \ No newline at end of file +``` + +Key observations: +* A complete and valid WoT Thing Description was generated and returned +* The `properties` section describes each attribute with its data type, read/write capabilities, and API forms +* The `actions` section defines invocable operations with their HTTP endpoints +* The `forms` section at root level provides bulk operations (read/write all properties, observe, subscribe) +* The `links` section contains `"item"` relations pointing to each Feature's TD (relative to the `base` URL) +* The `uriVariables` define query parameters (`channel`, `timeout`, `response-required`, `fields`) +* The `schemaDefinitions` provide a `dittoError` schema for error responses + +## Summary + +In this walkthrough you: + +1. **Created a Thing** with just a `definition` URL -- Ditto generated all attributes, features, and properties +2. **Retrieved a Thing Description** that documents every property, action, and event with concrete API endpoints +3. **Retrieved a Feature Description** that provides detailed interaction affordances including data types, constraints, and API forms + +The generated Thing Descriptions are fully compliant WoT TDs that any WoT-compatible tool or library can consume. + +## Further reading + +* [WoT Overview](basic-wot-integration.html) -- concepts and configuration +* [WoT Validation Configuration](basic-wot-validation-config.html) -- runtime validation API +* [W3C WoT Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/) -- the specification +* [Eclipse edi{TD}or](https://eclipse.github.io/editdor/) -- online WoT model editor diff --git a/documentation/src/main/resources/pages/ditto/basic-wot-integration.md b/documentation/src/main/resources/pages/ditto/basic-wot-integration.md index 15c984aa000..568f6d37f44 100644 --- a/documentation/src/main/resources/pages/ditto/basic-wot-integration.md +++ b/documentation/src/main/resources/pages/ditto/basic-wot-integration.md @@ -13,8 +13,7 @@ Using this integration, Ditto managed digital twins can be linked to WoT "Thing WoT "Thing Descriptions" containing the API descriptions of the twins. The WoT integration is considered stable and therefore active by default starting with Ditto version `3.0.0`. -If it should be disabled, it can be deactivated via a "feature toggle": -In order to deactivate the WoT integration, configure the following environment variable for all Ditto services: +To disable the WoT integration, configure the following environment variable for all Ditto services: ```bash DITTO_DEVOPS_FEATURE_WOT_INTEGRATION_ENABLED=false @@ -49,12 +48,12 @@ But also other protocol bindings may be defined in a `form`, e.g. MQTT or CoAP. The "WoT Thing Description" specification version 1.0 was already published as ["W3C Recommendation" in April 2020](https://www.w3.org/TR/wot-thing-description/), the next version 1.1 adds the -concept of "Thing Models" (TM) which can be seen as a template for generating "Thing Descriptions" but without some of +concept of "Thing Models" (TM) which serves as a template for generating "Thing Descriptions" but without some of its mandatory fields, e.g. `forms` including the protocol bindings. With the addition of the "Thing Model" concept, WoT becomes a perfect fit for describing the capabilities of [Digital Twins](intro-digitaltwins.html) managed in Ditto. -It is completely optional and even possible as "retrofit" model addition for already connected devices / already existing twins. +You can optionally add it as a "retrofit" model addition for already connected devices / already existing twins. The benefits of adding such a "Thing Model" reference to digital twins managed in Ditto are: * possibility to define model for data (Ditto Thing `attributes` + Ditto Feature `properties`), e.g. containing: @@ -103,7 +102,7 @@ This description contains not only the interaction capabilities (`properties` th ### WoT Thing Model -A [Thing Model](https://www.w3.org/TR/wot-thing-description11/#introduction-tm) can be seen as the model +A [Thing Model](https://www.w3.org/TR/wot-thing-description11/#introduction-tm) serves as the model (or interface in OOP terminology) for a potentially huge population of instances (Thing Descriptions) all "implementing" this contract. It does not need to contain the instance specific parts which a TD must include (e.g. `security` definitions or `forms`). @@ -254,8 +253,8 @@ This table shows an overview of how those elements map to Ditto concepts for the |---------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| | [Thing](https://www.w3.org/TR/wot-thing-description11/#thing) | [Ditto Thing](basic-thing.html) | | [Properties](https://www.w3.org/TR/wot-thing-description11/#propertyaffordance) | Thing [attributes](basic-thing.html#attributes) | -| [Actions](https://www.w3.org/TR/wot-thing-description11/#actionaffordance) | Thing [messages](basic-messages.html#elements) with **Direction** *to* (messages in the "inbox") of a Thing ID. | -| [Events](https://www.w3.org/TR/wot-thing-description11/#eventaffordance) | Thing [messages](basic-messages.html#elements) with **Direction** *from* (messages in the "outbox") of a Thing ID. | +| [Actions](https://www.w3.org/TR/wot-thing-description11/#actionaffordance) | Thing [messages](basic-messages.html#message-elements) with **Direction** *to* (messages in the "inbox") of a Thing ID. | +| [Events](https://www.w3.org/TR/wot-thing-description11/#eventaffordance) | Thing [messages](basic-messages.html#message-elements) with **Direction** *from* (messages in the "outbox") of a Thing ID. | | [Composition via `tm:submodel`](https://www.w3.org/TR/wot-thing-description11/#thing-model-composition) | Thing [features](basic-thing.html#features) representing different aspects of a Ditto Thing. | @@ -267,8 +266,8 @@ This table shows an overview of how those elements map to Ditto concepts for the |---------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [Thing](https://www.w3.org/TR/wot-thing-description11/#thing) | Feature.
    In Ditto, a Feature is an aspect of a [Ditto Thing](basic-thing.html). As the Feature is defined by its properties and messages it supports, it maps to a WoT Thing. | | [Properties](https://www.w3.org/TR/wot-thing-description11/#propertyaffordance) | Feature [properties](basic-feature.html#feature-properties) | -| [Actions](https://www.w3.org/TR/wot-thing-description11/#actionaffordance) | Feature [messages](basic-messages.html#elements) with **Direction** *to* (messages in the "inbox") of a Thing ID + Feature ID combination. | -| [Events](https://www.w3.org/TR/wot-thing-description11/#eventaffordance) | Feature [messages](basic-messages.html#elements) with **Direction** *from* (messages in the "outbox") of a Thing ID + Feature ID combination. | +| [Actions](https://www.w3.org/TR/wot-thing-description11/#actionaffordance) | Feature [messages](basic-messages.html#message-elements) with **Direction** *to* (messages in the "inbox") of a Thing ID + Feature ID combination. | +| [Events](https://www.w3.org/TR/wot-thing-description11/#eventaffordance) | Feature [messages](basic-messages.html#message-elements) with **Direction** *from* (messages in the "outbox") of a Thing ID + Feature ID combination. | ## Integration in Ditto @@ -444,7 +443,7 @@ The implementation supports validation or enforcing the following Ditto concepts follow the contract defined by the TM `properties` * by default, non-modeled `attributes` are forbidden to be created/updated * `attributes` which are not marked as optional (via `"tm:optional"`) are ensured not to be deleted - * Thing [messages](basic-messages.html#sending-messages): + * Thing [messages](basic-messages.html#sending-and-receiving-messages): * based on the linked TM of a Thing's [definition](basic-thing.html#definition), the Thing `messages` * sent to its `inbox` have to follow the contract defined by the TM `actions` * both, the defined `input` payload and the `output` payload is validated @@ -461,7 +460,7 @@ The implementation supports validation or enforcing the following Ditto concepts * Feature [properties](basic-feature.html#feature-properties): * based on the linked TM of a Feature's [definition](basic-feature.html#feature-definition), the Feature `properties` have to follow the contract defined by the TM `properties` - * Feature [messages](basic-messages.html#sending-messages): + * Feature [messages](basic-messages.html#sending-and-receiving-messages): * based on the linked TM of a Feature's [definition](basic-feature.html#feature-definition), the Feature `messages` * sent to its `inbox` have to follow the contract defined by the TM `actions` * both, the defined `input` payload and the `output` payload is validated @@ -684,7 +683,7 @@ To use the Ditto WoT extension in your Thing Models or Thing Descriptions, add i } ``` -### Ditto WoT Extension: category +### The `category` term The [category](https://ditto.eclipseprojects.io/wot/ditto-extension#category) is a term which can be added in scope of WoT TM/TD [Property Affordances](https://www.w3.org/TR/wot-thing-description11/#propertyaffordance). @@ -750,7 +749,7 @@ Based on the example above, a generated feature JSON would for example look like } ``` -### Ditto WoT Extension: deprecationNotice +### The `deprecationNotice` term The [deprecationNotice](https://ditto.eclipseprojects.io/wot/ditto-extension#deprecationNotice) is a term which can be added at the WoT TM/TD (Thing) level, or in scope of @@ -1055,3 +1054,10 @@ wot-directory { The example can be found on a [dedicated page](basic-wot-integration-example.html) as the JSONs included in the example are quite long. + +## Further reading + +* [WoT Integration Example](basic-wot-integration-example.html) -- step-by-step walkthrough +* [WoT Validation Configuration](basic-wot-validation-config.html) -- runtime validation API +* [W3C WoT Thing Description 1.1](https://www.w3.org/TR/wot-thing-description11/) -- the specification +* [Web of Things in a Nutshell](https://www.w3.org/WoT/documentation/) -- W3C introduction diff --git a/documentation/src/main/resources/pages/ditto/basic-wot-validation-config.md b/documentation/src/main/resources/pages/ditto/basic-wot-validation-config.md index d7eee5d0d77..97fce2f5041 100644 --- a/documentation/src/main/resources/pages/ditto/basic-wot-validation-config.md +++ b/documentation/src/main/resources/pages/ditto/basic-wot-validation-config.md @@ -1,42 +1,58 @@ --- -title: WoT Validation Config API +title: WoT Validation Configuration keywords: WoT, validation, config, API, DData, distribution, recovery, example tags: [wot] permalink: basic-wot-validation-config.html --- -# WoT Validation Config API +You manage WoT Thing Model validation rules at runtime through the Ditto devops API, enabling dynamic control over which validation checks apply to specific users, models, or deployment stages. -This page documents the management of WoT (Web of Things) validation configuration in Eclipse Ditto, including API endpoints, dynamic configuration, distributed state, recovery, and practical examples. +{% include callout.html content="**TL;DR**: Use `PUT /devops/wot/config` to set global validation rules, and `PUT /devops/wot/config/dynamicConfigs/{scopeId}` to override rules for specific users or models. This is especially useful during model migrations." type="primary" %} -## Introduction +## Overview -Eclipse Ditto allows you to configure WoT Thing Model validation both statically (via config files or environment variables) and dynamically at runtime via HTTP API endpoints. This enables flexible, fine-grained control over validation behavior in distributed deployments. +Eclipse Ditto supports both static WoT validation (via config files or environment variables) and dynamic runtime configuration via HTTP API endpoints. Dynamic configuration lets you: -## Use case: Migrating to a new WoT model version +* Temporarily relax validation during model migrations +* Apply different validation rules per user, connection, or model +* Adjust validation without restarting Ditto -When you update your WoT Thing Model to a new version, some existing Things or updates may not immediately comply with the new, stricter validation rules. Ditto's dynamic validation config endpoint allows you to temporarily relax validation for specific Things. This means you can: +## How it works -- Allow updates that would otherwise be rejected as invalid under the new model, enabling a smooth transition period. -- Gradually adapt your data or processes to the new model requirements without downtime or disruption. -- Once migration is complete and all data is compliant, you can restore strict validation to enforce the new model for all future updates. +### Use case: model migration -This approach helps ensure a seamless migration to new WoT model versions, minimizing interruptions and validation errors during the transition. +When you upgrade a WoT Thing Model to a new version, existing Things may not immediately comply with stricter validation rules. The dynamic validation API lets you: -## API Endpoints +1. Temporarily relax validation for affected Things +2. Migrate data and processes to the new model +3. Restore strict validation once migration is complete -| Endpoint | Method | Purpose | -|----------|--------|-------------------------------------------------------------------| -| `/devops/wot/config` | GET | Get the current global WoT validation config | -| `/devops/wot/config` | PUT | Upsert (create or update) the global WoT validation config | -| `/devops/wot/config` | DELETE | Delete the global WoT validation config (revert to static config) | -| `/devops/wot/config/merged` | GET | Get the merged (static + dynamic) config | -| `/devops/wot/config/dynamicConfigs` | GET | List all dynamic config sections | -| `/devops/wot/config/dynamicConfigs/{scopeId}` | GET | Get a specific dynamic config section | -| `/devops/wot/config/dynamicConfigs/{scopeId}` | PUT | Upsert (create or update) a dynamic config section | -| `/devops/wot/config/dynamicConfigs/{scopeId}` | DELETE | Delete a dynamic config section | +### Configuration layers -## Example: Enable WoT Validation Globally +Ditto evaluates validation rules in layers: + +1. **Static config** -- defined in `things.conf` or environment variables +2. **Global dynamic config** -- set via the `/devops/wot/config` API +3. **Scoped dynamic overrides** -- set via `/devops/wot/config/dynamicConfigs/{scopeId}` for specific contexts + +The **merged config** (viewable at `/devops/wot/config/merged`) is what Ditto actually uses for validation. + +## API endpoints + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/devops/wot/config` | GET | Get the current global WoT validation config | +| `/devops/wot/config` | PUT | Create or update the global config | +| `/devops/wot/config` | DELETE | Delete the global config (revert to static config) | +| `/devops/wot/config/merged` | GET | Get the effective merged config | +| `/devops/wot/config/dynamicConfigs` | GET | List all dynamic config sections | +| `/devops/wot/config/dynamicConfigs/{scopeId}` | GET | Get a specific dynamic config section | +| `/devops/wot/config/dynamicConfigs/{scopeId}` | PUT | Create or update a dynamic config section | +| `/devops/wot/config/dynamicConfigs/{scopeId}` | DELETE | Delete a dynamic config section | + +## Examples + +### Enable global validation ```http PUT /devops/wot/config @@ -70,7 +86,9 @@ Content-Type: application/json } ``` -## Example: Add a Dynamic Override for a Specific User +### Add a dynamic override for a specific user + +This example disables validation and switches to warning-only mode for an admin user: ```http PUT /devops/wot/config/dynamicConfigs/my-scope @@ -92,33 +110,30 @@ Content-Type: application/json } ``` -## How Dynamic Config Works - -- **Dynamic config sections** allow you to override the global validation config for specific API calls, based on: - - Ditto headers (e.g., user or connection) - - Thing or Feature model URLs -- The **merged config** is what Ditto actually uses for validation, combining static, global, and dynamic settings. +### How dynamic overrides match -## API Schema +Dynamic config sections match API calls based on three criteria (all must match for an override to apply): -For details on the request/response structure, see the [Ditto HTTP API reference](/http-api-doc.html) or the Ditto API documentation. +| Criteria | Description | +|----------|-------------| +| `ditto-headers-patterns` | Match Ditto headers (e.g., `ditto-originator` for user or connection identity). Multiple header blocks are OR-combined; fields within a block are AND-combined. | +| `thing-definition-patterns` | Match the Thing's WoT Thing Model URL (OR-combined) | +| `feature-definition-patterns` | Match the Feature's WoT Thing Model URL (OR-combined) | ## Technical details -### Distributed State and Recovery - -In a clustered Ditto deployment, the WoT validation configuration is managed as distributed state using [Pekko Distributed Data (DData)](https://pekko.apache.org/docs/pekko/current/typed/distributed-data.html). This ensures that configuration changes are automatically replicated across all nodes in the cluster, providing consistency, high availability, and resilience. - -#### DData-based Distribution +### Distributed state and recovery -- **Replication:** Whenever the WoT validation config is created, updated, or deleted (including dynamic config sections), the change is published to the DData replicator. All Ditto nodes with the `wot-validation-config-aware` cluster role receive and apply the update. -- **Consistency:** DData uses CRDTs (Conflict-free Replicated Data Types) to ensure eventual consistency across the cluster, even in the presence of network partitions or node failures. -- **Immediate Effect:** API changes to the config (via the endpoints described above) are visible to all nodes almost immediately, ensuring that validation behavior is consistent regardless of which node handles a request. +In a clustered Ditto deployment, the WoT validation configuration uses [Pekko Distributed Data (DData)](https://pekko.apache.org/docs/pekko/current/typed/distributed-data.html) for replication: -#### Recovery and Database Synchronization +* **Replication** -- configuration changes propagate to all nodes with the `wot-validation-config-aware` cluster role +* **Consistency** -- DData uses CRDTs (Conflict-free Replicated Data Types) for eventual consistency +* **Persistence** -- configuration is also stored in MongoDB via event sourcing and snapshots +* **Recovery** -- on startup, each node recovers the latest configuration from the database and publishes it to DData +* **Resilience** -- rolling restarts and failover do not result in configuration loss -- **Persistence:** The WoT validation config is also persisted in the database (MongoDB) using event sourcing and snapshots. This ensures that the config survives full cluster restarts or catastrophic failures. -- **Startup Recovery:** On startup, each node recovers the latest WoT validation config from the database. Once recovery is complete, the config is published to DData, ensuring that all nodes are synchronized with the most recent state. -- **Rolling Restarts and Failover:** Because the config is both persisted and distributed, rolling restarts or failover scenarios do not result in loss of configuration. Any node joining the cluster will receive the current config from DData or recover it from the database if needed. +## Further reading -This architecture ensures that the WoT validation configuration is always up-to-date, consistent, and highly available, even in the face of failures, restarts, or network issues. \ No newline at end of file +* [WoT Overview](basic-wot-integration.html) -- WoT integration concepts and static configuration +* [WoT Integration Example](basic-wot-integration-example.html) -- hands-on walkthrough +* [Ditto HTTP API reference](http-api-doc.html) -- full API schema details diff --git a/documentation/src/main/resources/pages/ditto/client-sdk-java.md b/documentation/src/main/resources/pages/ditto/client-sdk-java.md index ddc90f6aa27..771cdf61f30 100644 --- a/documentation/src/main/resources/pages/ditto/client-sdk-java.md +++ b/documentation/src/main/resources/pages/ditto/client-sdk-java.md @@ -1,30 +1,30 @@ --- -title: Client SDK Java -keywords: +title: Java SDK +keywords: tags: [client_sdk] permalink: client-sdk-java.html --- -A client SDK for Java in order to interact with digital twins provided by an Eclipse Ditto backend. +The Ditto Java client SDK lets you manage digital twins, receive change notifications, exchange messages, and search for Things -- all from Java applications. -## Features +{% include callout.html content="**TL;DR**: Add the `ditto-client` Maven dependency, create a `DittoClient` instance with WebSocket messaging, and use `client.twin()`, `client.live()`, and `client.policies()` to interact with your Ditto backend." type="primary" %} -* Digital twin management: CRUD (create, read, update, delete) of Ditto [things](https://www.eclipse.dev/ditto/basic-thing.html) -* [Change notifications](https://www.eclipse.dev/ditto/basic-changenotifications.html): - consume notifications whenever a "watched" digital twin is modified -* Send/receive [messages](https://www.eclipse.dev/ditto/basic-messages.html) to/from devices connected via a digital twin -* Use the [live channel](https://www.eclipse.dev/ditto/protocol-twinlive.html#live) in order to react on commands directed - to devices targeting their "live" state +## Overview -## Communication channel +The Java SDK communicates with Ditto over [WebSocket](httpapi-protocol-bindings-websocket.html) using the [Ditto Protocol](protocol-overview.html). It provides a type-safe, asynchronous API for: -The Ditto Java client interacts with an Eclipse Ditto backend via Ditto's -[WebSocket](https://www.eclipse.dev/ditto/httpapi-protocol-bindings-websocket.html) sending and receiving messages -in [Ditto Protocol](https://www.eclipse.dev/ditto/protocol-overview.html). +* **Twin management** -- create, read, update, and delete [Things](basic-thing.html) +* **Change notifications** -- receive events when digital twins are modified +* **Messages** -- send and receive [messages](basic-messages.html) to/from devices +* **Live channel** -- interact with devices via the [live channel](protocol-twinlive.html) +* **Policy management** -- manage [Policies](basic-policy.html) +* **Search** -- find Things using [RQL](basic-rql.html) expressions -## Usage +## Getting started -Maven coordinates: +### Installation + +Add the Maven dependency: ```xml @@ -34,241 +34,273 @@ Maven coordinates: ``` -### Instantiate & configure a new Ditto client - -To configure your Ditto client instance, use the `org.eclipse.ditto.client.configuration` package in order to -* create instances of `AuthenticationProvider` and `MessagingProvider` -* create a `DisconnectedDittoClient` instance -* obtain a `DittoClient` instance asynchronously by calling `.connect()` +### Create a client instance -For example: +Configure authentication and messaging, then connect: ```java -ProxyConfiguration proxyConfiguration = - ProxyConfiguration.newBuilder() - .proxyHost("localhost") - .proxyPort(3128) - .build(); - AuthenticationProvider authenticationProvider = - AuthenticationProviders.clientCredentials(ClientCredentialsAuthenticationConfiguration.newBuilder() - .clientId("my-oauth-client-id") - .clientSecret("my-oauth-client-secret") - .scopes("offline_access email") - .tokenEndpoint("https://my-oauth-provider/oauth/token") - // optionally configure a proxy server - .proxyConfiguration(proxyConfiguration) - .build()); + AuthenticationProviders.clientCredentials( + ClientCredentialsAuthenticationConfiguration.newBuilder() + .clientId("my-oauth-client-id") + .clientSecret("my-oauth-client-secret") + .scopes("offline_access email") + .tokenEndpoint("https://my-oauth-provider/oauth/token") + .build()); MessagingProvider messagingProvider = - MessagingProviders.webSocket(WebSocketMessagingConfiguration.newBuilder() - .endpoint("wss://ditto.eclipseprojects.io") - // optionally configure a proxy server or a truststore containing the trusted CAs for SSL connection establishment - .proxyConfiguration(proxyConfiguration) - .trustStoreConfiguration(TrustStoreConfiguration.newBuilder() - .location(TRUSTSTORE_LOCATION) - .password(TRUSTSTORE_PASSWORD) - .build()) - .build(), authenticationProvider); - -DisconnectedDittoClient disconnectedDittoClient = DittoClients.newInstance(messagingProvider); + MessagingProviders.webSocket( + WebSocketMessagingConfiguration.newBuilder() + .endpoint("wss://ditto.eclipseprojects.io") + .build(), + authenticationProvider); + +DisconnectedDittoClient disconnectedDittoClient = + DittoClients.newInstance(messagingProvider); disconnectedDittoClient.connect() .thenAccept(this::startUsingDittoClient) .exceptionally(error -> disconnectedDittoClient.destroy()); +``` +You can optionally configure a proxy and TLS trust store: + +```java +ProxyConfiguration proxyConfiguration = + ProxyConfiguration.newBuilder() + .proxyHost("localhost") + .proxyPort(3128) + .build(); + +// Add to authentication configuration: +// .proxyConfiguration(proxyConfiguration) + +// Add to messaging configuration: +// .proxyConfiguration(proxyConfiguration) +// .trustStoreConfiguration(TrustStoreConfiguration.newBuilder() +// .location(TRUSTSTORE_LOCATION) +// .password(TRUSTSTORE_PASSWORD) +// .build()) ``` -### Use the Ditto client +## Examples + +### Manage twins -#### Manage twins +Create a Thing and set an attribute: ```java -client.twin().create("org.eclipse.ditto:new-thing").handle((createdThing, throwable) -> { - if (createdThing != null) { - System.out.println("Created new thing: " + createdThing); - } else { - System.out.println("Thing could not be created due to: " + throwable.getMessage()); - } - return client.twin().forId(thingId).putAttribute("first-updated-at", OffsetDateTime.now().toString()); -}).toCompletableFuture().get(); // this will block the thread! work asynchronously whenever possible! +client.twin().create("org.eclipse.ditto:new-thing") + .handle((createdThing, throwable) -> { + if (createdThing != null) { + System.out.println("Created: " + createdThing); + } else { + System.out.println("Error: " + throwable.getMessage()); + } + return client.twin().forId(thingId) + .putAttribute("first-updated-at", + OffsetDateTime.now().toString()); + }) + .toCompletableFuture() + .get(); // blocks -- work asynchronously when possible ``` -#### Subscribe for change notifications +### Subscribe for change notifications -In order to subscribe for [events](basic-signals-event.html) emitted by Ditto after a twin was modified, start the -consumption on the `twin` channel: +Listen for Thing modifications: ```java -client.twin().startConsumption().toCompletableFuture().get(); // this will block the thread! work asynchronously whenever possible! -System.out.println("Subscribed for Twin events"); -client.twin().registerForThingChanges("my-changes", change -> { - if (change.getAction() == ChangeAction.CREATED) { - System.out.println("An existing Thing was modified: " + change.getThing()); - // perform custom actions .. - } -}); +client.twin().startConsumption() + .toCompletableFuture().get(); + +client.twin().registerForThingChanges("my-changes", + change -> { + if (change.getAction() == ChangeAction.CREATED) { + System.out.println("Thing modified: " + + change.getThing()); + } + }); ``` -There is also the possibility here to apply *server side filtering* of which events will get delivered to the client: +Apply server-side filtering to receive only relevant events: ```java client.twin().startConsumption( - Options.Consumption.filter("gt(features/temperature/properties/value,23.0)") -).toCompletableFuture().get(); // this will block the thread! work asynchronously whenever possible! -System.out.println("Subscribed for Twin events"); -client.twin().registerForFeaturePropertyChanges("my-feature-changes", "temperature", "value", change -> { - // perform custom actions .. -}); + Options.Consumption.filter( + "gt(features/temperature/properties/value,23.0)") +).toCompletableFuture().get(); + +client.twin().registerForFeaturePropertyChanges( + "my-feature-changes", "temperature", "value", + change -> { /* handle change */ }); ``` -##### Subscribe to enriched change notifications +#### Enriched change notifications -In order to use [enrichment](basic-enrichment.html) in the Ditto Java client, the `startConsumption()` call can be -enhanced with the additional extra fields: +Request [extra fields](basic-enrichment.html) with each notification: ```java client.twin().startConsumption( - Options.Consumption.extraFields(JsonFieldSelector.newInstance("attributes/location")) -).toCompletableFuture().get(); // this will block the thread! work asynchronously whenever possible! -client.twin().registerForThingChanges("my-enriched-changes", change -> { - Optional extra = change.getExtra(); - // perform custom actions, making use of the 'extra' data .. -}); + Options.Consumption.extraFields( + JsonFieldSelector.newInstance("attributes/location")) +).toCompletableFuture().get(); + +client.twin().registerForThingChanges("my-enriched-changes", + change -> { + Optional extra = change.getExtra(); + // use extra data + }); ``` -In combination with a `filter`, the extra fields may also be used as part of such a filter: +Combine enrichment with a filter to use extra fields in the filter expression: ```java client.twin().startConsumption( - Options.Consumption.extraFields(JsonFieldSelector.newInstance("attributes/location")), - Options.Consumption.filter("eq(attributes/location,\"kitchen\")") -).toCompletableFuture().get(); // this will block the thread! work asynchronously whenever possible! -// register the callbacks... + Options.Consumption.extraFields( + JsonFieldSelector.newInstance("attributes/location")), + Options.Consumption.filter("eq(attributes/location,\"kitchen\")") +).toCompletableFuture().get(); ``` +### Send and receive messages -#### Send/receive messages - -Register for receiving messages with the subject `hello.world` on any thing: +Register a handler for messages with subject `hello.world`: ```java -client.live().startConsumption().toCompletableFuture().get(); // this will block the thread! work asynchronously whenever possible! -System.out.println("Subscribed for live messages/commands/events"); -client.live().registerForMessage("globalMessageHandler", "hello.world", message -> { - System.out.println("Received Message with subject " + message.getSubject()); - message.reply() - .statusCode(HttpStatusCode.IM_A_TEAPOT) - .payload("Hello, I'm just a Teapot!") - .send(); -}); +client.live().startConsumption() + .toCompletableFuture().get(); + +client.live().registerForMessage( + "globalMessageHandler", "hello.world", + message -> { + System.out.println("Received: " + message.getSubject()); + message.reply() + .statusCode(HttpStatusCode.IM_A_TEAPOT) + .payload("Hello, I'm just a Teapot!") + .send(); + }); ``` -Send a message with the subject `hello.world` to the thing with ID `org.eclipse.ditto:new-thing`: +Send a message to a specific Thing: ```java client.live().forId("org.eclipse.ditto:new-thing") - .message() - .from() - .subject("hello.world") - .payload("I am a Teapot") - .send(String.class, (response, throwable) -> - System.out.println("Got response: " + response.getPayload().orElse(null)) - ); + .message() + .from() + .subject("hello.world") + .payload("I am a Teapot") + .send(String.class, (response, throwable) -> + System.out.println("Response: " + + response.getPayload().orElse(null))); ``` -#### Manage policies +### Manage policies -Read a policy: -```java -Policy retrievedPolicy = client.policies().retrieve(PolicyId.of("org.eclipse.ditto:new-policy")) - .toCompletableFuture().get(); // this will block the thread! work asynchronously whenever possible! -``` +Read and create Policies: -Create a policy: ```java -Policy newPolicy = Policy.newBuilder(PolicyId.of("org.eclipse.ditto:new-policy")) - .forLabel("DEFAULT") - .setSubject(Subject.newInstance(SubjectIssuer.newInstance("nginx"), "ditto")) - .setGrantedPermissions(PoliciesResourceType.policyResource("/"), "READ", "WRITE") - .setGrantedPermissions(PoliciesResourceType.thingResource("/"), "READ", "WRITE") - .build(); +// Read +Policy policy = client.policies() + .retrieve(PolicyId.of("org.eclipse.ditto:new-policy")) + .toCompletableFuture().get(); + +// Create +Policy newPolicy = Policy.newBuilder( + PolicyId.of("org.eclipse.ditto:new-policy")) + .forLabel("DEFAULT") + .setSubject(Subject.newInstance( + SubjectIssuer.newInstance("nginx"), "ditto")) + .setGrantedPermissions( + PoliciesResourceType.policyResource("/"), + "READ", "WRITE") + .setGrantedPermissions( + PoliciesResourceType.thingResource("/"), + "READ", "WRITE") + .build(); client.policies().create(newPolicy) - .toCompletableFuture().get(); // this will block the thread! work asynchronously whenever possible! + .toCompletableFuture().get(); ``` -Updating and deleting policies is also possible via the Java client API, please follow the API and the JavaDoc. +### Search for Things -#### Search for things +Use the Java Stream API: -Search for things using the Java 8 `java.util.Stream` API: ```java client.twin().search() - .stream(queryBuilder -> queryBuilder.namespace("org.eclipse.ditto") - .filter("eq(attributes/location,'kitchen')") // apply RQL expression here - .options(builder -> builder.sort(s -> s.desc("thingId")).size(1)) - ) - .forEach(foundThing -> System.out.println("Found thing: " + foundThing)); + .stream(queryBuilder -> queryBuilder + .namespace("org.eclipse.ditto") + .filter("eq(attributes/location,'kitchen')") + .options(builder -> builder + .sort(s -> s.desc("thingId")) + .size(1))) + .forEach(thing -> + System.out.println("Found: " + thing)); ``` -Use an [RQL](basic-rql.html) query in order to filter for the searched things. +Or use the reactive streams Publisher API: -Search for things using the reactive streams `org.reactivestreams.Publisher` API: ```java Publisher> publisher = client.twin().search() - .publisher(queryBuilder -> queryBuilder.namespace("org.eclipse.ditto") - .filter("eq(attributes/location,'kitchen')") // apply RQL expression here - .options(builder -> builder.sort(s -> s.desc("thingId")).size(1)) - ); + .publisher(queryBuilder -> queryBuilder + .namespace("org.eclipse.ditto") + .filter("eq(attributes/location,'kitchen')") + .options(builder -> builder + .sort(s -> s.desc("thingId")) + .size(1))); + // integrate the publisher in the reactive streams library of your choice, e.g. Pekko streams: -org.apache.pekko.stream.javadsl.Source things = org.apache.pekko.stream.javadsl.Source.fromPublisher(publisher) - .flatMapConcat(Source::from); +org.apache.pekko.stream.javadsl.Source things = + org.apache.pekko.stream.javadsl.Source.fromPublisher(publisher) + .flatMapConcat(Source::from); // .. proceed working with the Pekko Source .. ``` +### Request and issue acknowledgements -#### Request and issue acknowledgements - -[Requesting acknowledgements](basic-acknowledgements.html#requesting-acks) is possible in the Ditto Java -client in the following way: +Request [acknowledgements](basic-acknowledgements.html) for a command: ```java DittoHeaders dittoHeaders = DittoHeaders.newBuilder() - .acknowledgementRequest( - AcknowledgementRequest.of(DittoAcknowledgementLabel.PERSISTED), - AcknowledgementRequest.of(AcknowledgementLabel.of("my-custom-ack")) - ) - .timeout("5s") - .build(); - -client.twin().forId(ThingId.of("org.eclipse.ditto:my-thing")) - .putAttribute("counter", 42, Options.dittoHeaders(dittoHeaders)) - .whenComplete((aVoid, throwable) -> { - if (throwable instanceof AcknowledgementsFailedException) { - Acknowledgements acknowledgements = ((AcknowledgementsFailedException) throwable).getAcknowledgements(); - System.out.println("Acknowledgements could not be fulfilled: " + acknowledgements); - } - }); + .acknowledgementRequest( + AcknowledgementRequest.of( + DittoAcknowledgementLabel.PERSISTED), + AcknowledgementRequest.of( + AcknowledgementLabel.of("my-custom-ack"))) + .timeout("5s") + .build(); + +client.twin() + .forId(ThingId.of("org.eclipse.ditto:my-thing")) + .putAttribute("counter", 42, + Options.dittoHeaders(dittoHeaders)) + .whenComplete((aVoid, throwable) -> { + if (throwable instanceof AcknowledgementsFailedException) { + Acknowledgements acks = + ((AcknowledgementsFailedException) throwable) + .getAcknowledgements(); + System.out.println("Acks failed: " + acks); + } + }); ``` -[Issuing requested acknowledgements](basic-acknowledgements.html#issuing-acknowledgements) can be done like this -whenever a `Change` callback is invoked with a change notification: +Issue a custom acknowledgement when handling a change: ```java client.twin().registerForThingChanges("REG1", change -> { - change.handleAcknowledgementRequest(AcknowledgementLabel.of("my-custom-ack"), ackHandle -> - ackHandle.acknowledge(HttpStatusCode.NOT_FOUND, JsonObject.newBuilder() - .set("error-detail", "Could not be found") - .build() - ) - ); + change.handleAcknowledgementRequest( + AcknowledgementLabel.of("my-custom-ack"), + ackHandle -> ackHandle.acknowledge( + HttpStatusCode.NOT_FOUND, + JsonObject.newBuilder() + .set("error-detail", "Could not be found") + .build())); }); ``` +## Further reading -## Further examples - -For further examples on how to use the Ditto client, please have a look at the class -[DittoClientUsageExamples](https://github.com/eclipse-ditto/ditto-clients/blob/master/java/src/test/java/org/eclipse/ditto/client/DittoClientUsageExamples.java) - which is configured to connect to the [Ditto sandbox](https://ditto.eclipseprojects.io). +* [DittoClientUsageExamples](https://github.com/eclipse-ditto/ditto-clients/blob/master/java/src/test/java/org/eclipse/ditto/client/DittoClientUsageExamples.java) -- complete example code +* [Ditto sandbox](https://ditto.eclipseprojects.io) -- test environment for the examples +* [WebSocket binding](httpapi-protocol-bindings-websocket.html) -- underlying transport +* [Ditto Protocol](protocol-overview.html) -- message format reference diff --git a/documentation/src/main/resources/pages/ditto/client-sdk-javascript.md b/documentation/src/main/resources/pages/ditto/client-sdk-javascript.md index be88bf1130c..d011c13e560 100644 --- a/documentation/src/main/resources/pages/ditto/client-sdk-javascript.md +++ b/documentation/src/main/resources/pages/ditto/client-sdk-javascript.md @@ -1,63 +1,70 @@ --- -title: Client SDK JavaScript -keywords: +title: JavaScript SDK +keywords: tags: [client_sdk] permalink: client-sdk-javascript.html --- -A TypeScript library to facilitate working the the REST-like HTTP API and web socket API of Eclipse Ditto. +You use the Ditto JavaScript SDK to work with the Ditto HTTP API and WebSocket API from browser and Node.js environments. -## How to use it -Install `@eclipse-ditto/ditto-javascript-client-dom` for the DOM (browser) implementation, -`@eclipse-ditto/ditto-javascript-client-node` for the NodeJS implementation, or `@eclipse/ditto-javascript-client-api-ditto` for -the API and build your own client implementation. +{% include callout.html content="**TL;DR**: Install `@eclipse-ditto/ditto-javascript-client-dom` for browsers or `@eclipse-ditto/ditto-javascript-client-node` for Node.js, then use the client to manage Things, subscribe to events, and send messages." type="primary" %} -More information can be found in the descriptions of the subpackages: -* [@eclipse-ditto/ditto-javascript-client-api](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/api/README.md) -* [@eclipse-ditto/ditto-javascript-client-dom](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/dom/README.md) -* [@eclipse-ditto/ditto-javascript-client-node](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/node/README.md) +## Overview + +The JavaScript SDK provides separate packages for different environments: + +| Package | Environment | Description | +|---------|-------------|-------------| +| [`@eclipse-ditto/ditto-javascript-client-dom`](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/dom/README.md) | Browser (DOM) | Uses browser-native HTTP and WebSocket | +| [`@eclipse-ditto/ditto-javascript-client-node`](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/node/README.md) | Node.js | Uses Node.js HTTP and WebSocket | +| [`@eclipse-ditto/ditto-javascript-client-api`](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/api/README.md) | Any | API-only -- build your own client implementation | All released versions are published on [npmjs.com](https://www.npmjs.com/~eclipse_ditto). -## Compatibility with [Eclipse Ditto](https://github.com/eclipse-ditto/ditto) +## Getting started -The newest release of the JavaScript client will always try to cover as much API -functionality of the same Eclipse Ditto major version as possible. There might -however be missing features for which we would be very happy to accept contributions. +### Installation +Install the package for your environment: -## Coding +```bash +# For browser applications: +npm install @eclipse-ditto/ditto-javascript-client-dom + +# For Node.js applications: +npm install @eclipse-ditto/ditto-javascript-client-node ``` + +### Compatibility + +The JavaScript SDK tracks the same major version as Eclipse Ditto. The latest release covers as much API functionality as possible for the corresponding Ditto version. + +## Building from source + +```bash npm install npm run build npm run lint npm test -# or npm run test:watch ``` -## Troubleshooting -If you get strange errors, it would be best cleaning all dependencies and -starting from the beginning again: -``` +### Troubleshooting build issues + +If you encounter build errors, clean everything and start fresh: + +```bash npm run clean -# by hand delete node_modules in the root folder, or use a tool like rm, rimraf, etc. +# Delete node_modules in the root folder npm install npm run build -# ... ``` -It is important to know that during install and build some extra processes -are triggered by e.g. lerna which will symlink the `api` dependency into -the node_modules of `dom` and `node` packages. - -## Internals -This project is using [lerna](https://github.com/lerna/lerna) to split up the -client into different packages. This way we can have standalone codeable -subprojects (`api`, `dom` and `node`) but still are able to control dependencies, -build processes or release processes globally. - -Furthermore we use [rollup.js](https://rollupjs.org/) for providing multiple -module types of the packages, e.g. the `api` will be published as IIFE, -ES Module and CommonJS module. - -For automatically generating barrel files, [barrelsby](https://github.com/bencoveney/barrelsby) -is used during the build process. + +The build process uses [lerna](https://github.com/lerna/lerna) for multi-package management and [rollup.js](https://rollupjs.org/) for generating multiple module formats (IIFE, ES Module, CommonJS). During install and build, lerna symlinks the `api` dependency into the `dom` and `node` packages. + +## Further reading + +* [@eclipse-ditto/ditto-javascript-client-dom README](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/dom/README.md) -- browser usage details +* [@eclipse-ditto/ditto-javascript-client-node README](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/node/README.md) -- Node.js usage details +* [@eclipse-ditto/ditto-javascript-client-api README](https://github.com/eclipse-ditto/ditto-clients/blob/master/javascript/lib/api/README.md) -- API package details +* [HTTP API overview](httpapi-overview.html) -- the REST API the SDK wraps +* [WebSocket binding](httpapi-protocol-bindings-websocket.html) -- the WebSocket transport diff --git a/documentation/src/main/resources/pages/ditto/connectivity-header-mapping.md b/documentation/src/main/resources/pages/ditto/connectivity-header-mapping.md index f1aa276f5c9..a00908194eb 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-header-mapping.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-header-mapping.md @@ -1,44 +1,93 @@ --- -title: Header mapping for connections +title: Header Mapping keywords: header, mapping, placeholder tags: [connectivity] permalink: connectivity-header-mapping.html --- -When receiving messages from external systems or sending messages to external systems, the external headers of the -messages can be mapped to and from Ditto protocol headers. +Header mapping lets you translate between external message headers and Ditto protocol headers, enabling correlation IDs, content types, and custom metadata to flow through Ditto. -That way the headers can be passed through Ditto, or defined Ditto protocol headers like for example `correlation-id` -may be mapped to a header used for message correlation in the external system. +{% include callout.html content="**TL;DR**: Define a `headerMapping` object on any source or target to map external headers to/from Ditto protocol headers using placeholders." type="primary" %} -A header mapping can be defined individually for every source and target of a connection. For examples of a definition -see [source header mapping](basic-connections.html#source-header-mapping) -and [target header mapping](basic-connections.html#target-header-mapping). +## Overview + +When Ditto receives messages from or sends messages to external systems, you can map headers between +the external protocol and the Ditto protocol. This allows you to: + +* Pass headers through Ditto (for example, correlation IDs) +* Map protocol-specific headers to Ditto headers +* Set custom external headers on outgoing messages + +You define header mappings individually for each source and target. For configuration examples, see +[source header mapping](basic-connections.html#source-header-mapping) and +[target header mapping](basic-connections.html#target-header-mapping). {% include note.html content="Do not map headers prefixed by 'ditto-'. Ditto uses them internally. Setting them in header mapping has no effect." %} +## How it works + +A header mapping is a JSON object where: + +* **Keys** are the target header names +* **Values** are strings that can include [placeholders](basic-placeholders.html#scope-connections) + +For **inbound** messages (sources), the mapping translates external headers to Ditto protocol headers. +For **outbound** messages (targets), the mapping translates Ditto protocol headers to external headers. + +If a placeholder fails to resolve, that header is skipped. Other headers and the message itself +are still processed. + +## Configuration + +### Source header mapping example + +```json +{ + "addresses": ["telemetry"], + "authorizationContext": ["ditto:inbound-auth-subject"], + "headerMapping": { + "correlation-id": "{%raw%}{{ header:message-id }}{%endraw%}", + "content-type": "{%raw%}{{ header:content-type }}{%endraw%}", + "device-id": "{%raw%}{{ header:device_id }}{%endraw%}" + } +} +``` + +### Target header mapping example + +```json +{ + "address": "events/twin", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], + "headerMapping": { + "message-id": "{%raw%}{{ header:correlation-id }}{%endraw%}", + "content-type": "{%raw%}{{ header:content-type }}{%endraw%}", + "subject": "{%raw%}{{ topic:subject }}{%endraw%}", + "reply-to": "all-replies" + } +} +``` + ## Supported placeholders -The supported placeholders for header mapping are defined in the +The supported placeholders for header mapping are defined in the [Placeholders - Scope: Connections](basic-placeholders.html#scope-connections) section. -If a placeholder fails to resolve for a header value, then that header is not set. Placeholder resolution failure -does not prevent sending of the message or setting other headers with resolved values. ## Special header mapping keys -In addition to general header mapping capabilities, Ditto recognizes several special header mapping keys that control connectivity behavior: +Ditto recognizes several special header keys that control connectivity behavior. ### Response diversion headers -These headers control [response diversion](connectivity-response-diversion.html) functionality: +These headers control [response diversion](connectivity-response-diversion.html): -| Header Key | Description | Example Values | -|------------|-------------|----------------------------------------------------------------| -| `divert-response-to-connection` | Target connection ID for response diversion | `"target-connection-id"`, `"target-connection-id"` | -| `divert-expected-response-types` | Response types to divert (comma-separated) | `"response"`, `"error"`, `"nack"` | -| `diverted-response-from-connection` | Source connection of diverted response (automatically set) | `"source-connection-id"` | +| Header Key | Description | +|------------|-------------| +| `divert-response-to-connection` | Target connection ID for response diversion | +| `divert-expected-response-types` | Response types to divert (comma-separated: `response`, `error`, `nack`) | +| `diverted-response-from-connection` | Source connection of the diverted response (set automatically) | -Example source configuration with response diversion: ```json { "headerMapping": { @@ -51,9 +100,18 @@ Example source configuration with response diversion: ### Protocol-specific headers -Different protocols support different sets of headers. Some protocols also have special header behavior: +Different protocols support different header capabilities: + +* **AMQP 1.0** -- full application properties and message annotations +* **MQTT 5** -- user-defined properties +* **MQTT 3.1.1** -- no application headers (only special `mqtt.*` headers) +* **HTTP** -- standard HTTP headers plus `http.query` and `http.path` +* **Kafka** -- Kafka record headers + +{% include note.html content="Response diversion headers are processed by Ditto internally and are not sent as external protocol headers. They control internal routing behavior only." %} + +## Further reading -{% include note.html content="Response diversion headers (`ditto-divert-*`) -are processed by Ditto internally and are not sent as external protocol headers. -They control internal routing behavior only." -%} +* [Connections overview](basic-connections.html) -- source and target header mapping configuration +* [Placeholders](basic-placeholders.html) -- placeholder syntax and available placeholders +* [Response diversion](connectivity-response-diversion.html) -- redirect responses to other connections diff --git a/documentation/src/main/resources/pages/ditto/connectivity-hmac-signing.md b/documentation/src/main/resources/pages/ditto/connectivity-hmac-signing.md index ad11a5a2d1e..a6f96555c1b 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-hmac-signing.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-hmac-signing.md @@ -1,22 +1,29 @@ --- -title: HMAC signing +title: HMAC Signing keywords: hmac, hmac-sha256, authorization, azure, aws, azure iot hub, azure service bus, azure monitor, aws version 4 signing, aws sns, signing tags: [connectivity] permalink: connectivity-hmac-signing.html --- -## HMAC signing +You use HMAC signing to authenticate Ditto at external services like AWS and Azure without transmitting credentials directly. -Ditto provides an extensible framework for HMAC-based signing authentication processes for HTTP Push and +{% include callout.html content="**TL;DR**: Set `credentials.type` to `\"hmac\"` and specify an `algorithm` (`aws4-hmac-sha256`, `az-monitor-2016-04-01`, or `az-sasl`) with the required parameters. Supported on HTTP Push and AMQP 1.0 connections." type="primary" %} + +## Overview + +Ditto provides an extensible framework for HMAC-based signing authentication for HTTP Push and AMQP 1.0 connections. Three algorithms are available out of the box: -- `aws4-hmac-sha256` (HTTP Push only): [Version 4 request signing](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) for Amazon Web Services (AWS) -- `az-monitor-2016-04-01` (HTTP Push only): [Version 2016-04-01 request signing](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api#authorization) for Azure Monitor Data Collector -- `az-sasl` (HTTP Push and AMQP 1.0): [Shared Access Signatures](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-dev-guide-sas?tabs=node) for Azure IoT Hub -- `az-sasl` (HTTP Push only): [Shared Access Signatures](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-sas) for Azure Service Bus +| Algorithm | Connection types | Use case | +|-----------|-----------------|----------| +| `aws4-hmac-sha256` | HTTP Push | [AWS Version 4 signing](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) (SNS, S3, etc.) | +| `az-monitor-2016-04-01` | HTTP Push | [Azure Monitor Data Collector](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api#authorization) | +| `az-sasl` | HTTP Push, AMQP 1.0 | [Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-dev-guide-sas?tabs=node) and [Azure Service Bus](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-sas) Shared Access Signatures | + +## How it works +Set the `credentials` field in your connection configuration: -To use a request signing algorithm for authentication, set the `credentials` field of the connection as follows. {%raw%} ```json { @@ -24,126 +31,56 @@ To use a request signing algorithm for authentication, set the `credentials` fie "uri": "https://...:443", "credentials": { "type": "hmac", - "algorithm": "", // e.g.: "az-monitor-2016-04-01" + "algorithm": "", "parameters": { - // parameters of the algorithm named above. - ... + "...": "..." } - }, - ... + } } ``` {%endraw%} +## Configuration -### Pre-defined algorithms - -#### aws4-hmac-sha256 - -This algorithm works for AWS SNS and other services using [Version 4 request signing](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). - -The parameters of the algorithm `aws4-hmac-sha256` are: -- `region`: Region of the AWS endpoint. -- `service`: Service name of the AWS endpoint. -- `accessKey`: Access key of the signing user. -- `secretKey`: Secret key of the signing user. -- `doubleEncode`: Whether to double-encode and normalize path segments during request signing. Should be `false` for S3 and `true` for other services. Defaults to `true`. -- `canonicalHeaders`: Array of names of headers to include in the signature. Default to `["host"]`. -- `xAmzContentSha256`: Configuration for the header `x-amz-content-sha256`, which is mandatory for S3. Possible values are: - - `EXCLUDED`: Do not send the header for non-S3 services. This is the default. - - `INCLUDED`: Sign the payload hash as the value of the header for S3. - - `UNSIGNED`: Omit the payload hash in the signature for S3. - -#### az-monitor-2016-04-01 - -This algorithm works for [Version 2016-04-01 request signing](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api#authorization) -for Azure Monitor Data Collector. - -The parameters of the algorithm `az-monitor-2016-04-01` are: -- `workspaceId`: ID of the Azure Monitor workspace. -- `sharedKey`: Primary or secondary key of the Azure Monitor workspace. - -#### az-sasl - -This algorithm works for Azure IoT Hub (HTTP and AMQP) [Shared Access Signatures](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-dev-guide-sas?tabs=node) -and Azure Service Bus (HTTP) [Shared Access Signatures](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-sas). - -The parameters of the algorithm `az-sasl` are: -- `sharedKeyName`: Name of the used `sharedKey`. -- `sharedKey`: Primary or secondary key of `sharedKeyName`. The key for Azure Service Bus needs an additional - `Base64` encoding to work (e.g. a primary key `theKey` should be encoded to `dGhlS2V5` and used in this format). -- `endpoint`: The endpoint which is used in the signature. For Azure IoT Hub this is expected - to be the `resourceUri` without protocol (e.g. `myHub.azure-devices.net`, see the respective Azure documentation). - For Azure Service Bus, this is expected to be the full URI of the resource to which access - is claimed (e.g. `https://myNamespaces.servicebus.windows.net/myQueue`, see the respective Azure documentation) -- `ttl` (optional): The time to live of a signature as a string in duration format. Allowed time units are "ms" (milliseconds), - "s" (seconds), "m" (minutes) and "h" (hours), e.g. "10m" for ten minutes. - `ttl` should only be set for AMQP connections and defines how long the connection signing is valid. - The broker (e.g. Azure IoT Hub) will close the connection after `ttl` and Ditto will reconnect with a new signature. - Defaults to 7 days. - - -### Supported connection types - -| | HTTP Push connection | AMQP 1.0 connection | -| ------------------------ | -------------------- | ------------------- | -| `aws-hmac-sha256` | ✓ | | -| `az-monitor-2016-04-01` | ✓ | | -| `az-sasl` | ✓ | ✓ (for Azure IoT Hub) | - -### Configuration - -Algorithm names and implementations are configured in [`connectivity.conf`](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf). -The default configuration provides the names and implementations of the available pre-defined algorithms for the given -connection types. -```hocon -ditto.connectivity.connection { - http-push.hmac-algorithms = { - - aws4-hmac-sha256 = - "org.eclipse.ditto.connectivity.service.messaging.httppush.AwsRequestSigningFactory" - - az-monitor-2016-04-01 = - "org.eclipse.ditto.connectivity.service.messaging.httppush.AzMonitorRequestSigningFactory" +### aws4-hmac-sha256 - az-sasl = - "org.eclipse.ditto.connectivity.service.messaging.signing.AzSaslSigningFactory" +For AWS services using [Version 4 request signing](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html): - // my-own-request-signing-algorithm = - // "my.package.MyOwnImplementationOfHttpRequestSigningFactory" - } - amqp10.hmac-algorithms = { +| Parameter | Description | Default | +|-----------|-------------|---------| +| `region` | AWS region | -- | +| `service` | AWS service name | -- | +| `accessKey` | IAM access key | -- | +| `secretKey` | IAM secret key | -- | +| `doubleEncode` | Double-encode path segments (`false` for S3, `true` for others) | `true` | +| `canonicalHeaders` | Header names to include in the signature | `["host"]` | +| `xAmzContentSha256` | S3 payload hash header: `EXCLUDED`, `INCLUDED`, or `UNSIGNED` | `EXCLUDED` | - az-sasl = - "org.eclipse.ditto.connectivity.service.messaging.signing.AzSaslSigningFactory" +### az-monitor-2016-04-01 - // my-own-connection-signing-algorithm = - // "my.package.MyOwnImplementationOfAmqpConnectionSigningFactory" - } -} -``` +For [Azure Monitor Data Collector](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api#authorization): -Users may add own request signing algorithms by implementing a defined interface and providing the fully -qualified class name of the implementation in the config. The following table provides information where to update the configuration -and which interface needs to be implemented. +| Parameter | Description | +|-----------|-------------| +| `workspaceId` | Azure Monitor workspace ID | +| `sharedKey` | Primary or secondary workspace key | -| | HTTP Push connection | AMQP 1.0 connection | -| ------------------------ | -------------------- | ------------------- | -| Config path | `ditto.connectivity.connection.http-push.hmac-algorithms` | `ditto.connectivity.connection.amqp10.hmac-algorithms` | -| Class to implement | [HttpRequestSigningFactory](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpRequestSigningFactory.java) | [AmqpConnectionSigningFactory](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/messaging/amqp/AmqpConnectionSigningFactory.java) | +### az-sasl +For Azure IoT Hub and Azure Service Bus [Shared Access Signatures](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-dev-guide-sas?tabs=node): -### Example integrations +| Parameter | Description | +|-----------|-------------| +| `sharedKeyName` | Name of the shared access policy | +| `sharedKey` | Primary or secondary key (Base64-encode for Service Bus) | +| `endpoint` | Resource URI (for example, `myHub.azure-devices.net` or `https://myNs.servicebus.windows.net/myQueue`) | +| `ttl` (optional) | Signature lifetime (for example, `10m`). For AMQP connections, the broker closes the connection after TTL and Ditto reconnects. Default: 7 days. | -#### AWS SNS +## Examples -This example is an HTTP connection to [AWS SNS (Simple Notification Service)](https://aws.amazon.com/sns/) publishing -twin events as messages to an SNS topic. Prerequisites are: -- An IAM user with access to SNS, -- An SNS topic, -- A subscription on the SNS topic to receive twin events, e.g. by email. +### AWS SNS -The SNS connection is shown below. +Publish twin events to an [AWS SNS](https://aws.amazon.com/sns/) topic: {%raw%} ```json @@ -165,71 +102,42 @@ The SNS connection is shown below. }, "sources": [], "targets": [{ - "address": "GET:/", - "topics": [ "_/_/things/twin/events" ], - "authorizationContext": [ "integration:ditto" ], - "headerMapping": {}, - "payloadMapping": [ "javascript" ] + "address": "GET:/", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["integration:ditto"], + "payloadMapping": ["javascript"] }], - "clientCount": 1, - "failoverEnabled": true, - "validateCertificates": true, - "processorPoolSize": 5, - "specificConfig": { - "parallelism": "1" - }, + "specificConfig": { "parallelism": "1" }, "mappingDefinitions": { "javascript": { "mappingEngine": "JavaScript", "options": { - "incomingScript": "function mapToDittoProtocolMsg() {\n return undefined;\n}", - "outgoingScript": "function mapFromDittoProtocolMsg(namespace,name,group,channel,criterion,action,path,dittoHeaders,value,status,extra) {\n let textPayload = JSON.stringify(Ditto.buildDittoProtocolMsg(namespace, name, group, channel, criterion, action, path, dittoHeaders, value, status, extra));\n let query = 'Action=Publish&Message=' + encodeURIComponent(textPayload) + '&Subject=ThingModified&TopicArn=';\n let headers = {\"http.query\":query};\n return Ditto.buildExternalMsg(headers,'',null,'text/plain');\n}\n", - "loadBytebufferJS": "false", - "loadLongJS": "false" + "incomingScript": "function mapToDittoProtocolMsg() { return undefined; }", + "outgoingScript": "function mapFromDittoProtocolMsg(namespace,name,group,channel,criterion,action,path,dittoHeaders,value,status,extra) {\n let textPayload = JSON.stringify(Ditto.buildDittoProtocolMsg(namespace, name, group, channel, criterion, action, path, dittoHeaders, value, status, extra));\n let query = 'Action=Publish&Message=' + encodeURIComponent(textPayload) + '&Subject=ThingModified&TopicArn=';\n return Ditto.buildExternalMsg({\"http.query\":query},'',null,'text/plain');\n}" } } - }, - "tags": [] + } } ``` {%endraw%} -Parameters: -- ``: The region of the SNS service. -- `` The access key of the user authorized for SNS. -- `` The secret key of the user authorized for SNS. -- `` The ARN of the SNS topic. Note that it is a part of the URI and every `:` needs to be encoded as -`%3A`. - -Here is the outgoing payload mapping in a readable format. -Since the HTTP API of SNS requires GET requests with all necessary information in -the query parameters, the payload mapper computes the query string and sets it via -the [special header](connectivity-protocol-bindings-http.html#target-header-mapping) `http.query`. -It is important to set payload to `null` so that the HMAC -signature is computed from the SHA256 hash of the empty string, which SNS expects. -```js -function mapFromDittoProtocolMsg(namespace,name,group,channel,criterion, - action,path,dittoHeaders,value,status,extra) { +The outgoing mapper builds SNS query parameters and sets them via the `http.query` special header: + +```javascript +function mapFromDittoProtocolMsg(namespace, name, group, channel, criterion, + action, path, dittoHeaders, value, status, extra) { let textPayload = JSON.stringify(Ditto.buildDittoProtocolMsg(namespace, name, group, channel, criterion, action, path, dittoHeaders, value, status, extra)); let query = 'Action=Publish&Message=' + encodeURIComponent(textPayload) + '&Subject=ThingModified&TopicArn='; - let headers = { "http.query": query }; - return Ditto.buildExternalMsg(headers, '', null, 'text/plain'); + return Ditto.buildExternalMsg({ "http.query": query }, '', null, 'text/plain'); } ``` -#### AWS S3 +### AWS S3 -This example is an HTTP connection to [AWS S3 (Simple Storage Service)](https://aws.amazon.com/s3/) publishing -twin events as objects in an S3 bucket. Prerequisites are: -- An IAM user with access to S3, -- An S3 bucket. - -The S3 connection is shown below. The credentials parameter `"xAmzContentSha256": "INCLUDED"` is necessary in order -to include the payload hash as the value of the header `x-amz-content-sha256`. In addition, -the parameter `"doubleEncode"` must be set to `false` because S3 performs URI-encoding only once when computing -the signature, in contrast to other AWS services where the path segments of HTTP requests are encoded twice. +Publish twin events as objects in an [AWS S3](https://aws.amazon.com/s3/) bucket. Note the S3-specific +parameters `doubleEncode: false` and `xAmzContentSha256: "INCLUDED"`: {%raw%} ```json @@ -248,57 +156,36 @@ the signature, in contrast to other AWS services where the path segments of HTTP "accessKey": "", "secretKey": "", "doubleEncode": false, - "canonicalHeaders": [ "host", "x-amz-date" ], + "canonicalHeaders": ["host", "x-amz-date"], "xAmzContentSha256": "INCLUDED" } }, "sources": [], "targets": [{ - "address": "PUT:/", - "topics": [ "_/_/things/twin/events" ], - "authorizationContext": [ "integration:ditto" ], - "headerMapping": {}, - "payloadMapping": [ - "javascript" - ] + "address": "PUT:/", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["integration:ditto"], + "payloadMapping": ["javascript"] }], - "clientCount": 1, - "failoverEnabled": true, - "validateCertificates": true, - "processorPoolSize": 5, - "specificConfig": { - "parallelism": "1" - }, + "specificConfig": { "parallelism": "1" }, "mappingDefinitions": { "javascript": { "mappingEngine": "JavaScript", "options": { - "incomingScript": "function mapToDittoProtocolMsg() {\n return undefined;\n}\n", - "outgoingScript": "function mapFromDittoProtocolMsgWrapper(msg) {\n let topic = msg['topic'].split('/').join(':');\n let headers = {\n 'http.path': topic+':'+msg['revision']\n };\n let textPayload = JSON.stringify(msg);\n let bytePayload = null;\n let contentType = 'application/json';\n\n return Ditto.buildExternalMsg(\n headers,\n textPayload,\n bytePayload,\n contentType\n );\n}\n", - "loadBytebufferJS": "false", - "loadLongJS": "false" + "incomingScript": "function mapToDittoProtocolMsg() { return undefined; }", + "outgoingScript": "function mapFromDittoProtocolMsgWrapper(msg) {\n let topic = msg['topic'].split('/').join(':');\n let headers = { 'http.path': topic+':'+msg['revision'] };\n return Ditto.buildExternalMsg(headers, JSON.stringify(msg), null, 'application/json');\n}" } } - }, - "tags": [] + } } ``` {%endraw%} -Parameters: -- ``: Name of the S3 bucket. -- ``: The region of the S3 service. -- `` The access key of the user authorized for S3. -- `` The secret key of the user authorized for S3. - -In order to create a distinct object for each event, -the payload mapper that computes the path string and sets it via -the [special header](connectivity-protocol-bindings-http.html#target-header-mapping) `http.path`. -The mapper overrides the function `mapFromDittoProtocolMsgWrapper` instead of the usual -`mapFromDittoProtocolMsg` so that it has access to the revision number of twin events. -The value of `http.path` is the name of the object, which consists of the topic of the event with `/` replaced by `:` -followed by its revision. For example, a thing-modified event of revision 42 generates the object -`::things:twin:events:modified:42`. +The payload mapper creates a distinct S3 object per event by computing the path via the +[special header](connectivity-protocol-bindings-http.html#target-header-mapping) `http.path`. +It overrides `mapFromDittoProtocolMsgWrapper` instead of `mapFromDittoProtocolMsg` to access the revision number. +The object name consists of the event topic with `/` replaced by `:` followed by its revision (for example, +`::things:twin:events:modified:42`): ```js function mapFromDittoProtocolMsgWrapper(msg) { @@ -319,11 +206,9 @@ function mapFromDittoProtocolMsgWrapper(msg) { } ``` -#### Azure Monitor Data Collector HTTP API +### Azure Monitor Data Collector -This example is an HTTP connection pushing twin events into -[Azure Monitor Data Collector API](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api). -It requires an Azure Log Analytics Workspace. +Push twin events to [Azure Monitor](https://docs.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api): {%raw%} ```json @@ -343,38 +228,23 @@ It requires an Azure Log Analytics Workspace. }, "sources": [], "targets": [{ - "address": "POST:/api/logs?api-version=2016-04-01", - "topics": [ "_/_/things/twin/events" ], - "authorizationContext": [ "integration:ditto" ], - "headerMapping": { - "Content-Type": "application/json", - "Log-Type": "TwinEvent" - } + "address": "POST:/api/logs?api-version=2016-04-01", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["integration:ditto"], + "headerMapping": { + "Content-Type": "application/json", + "Log-Type": "TwinEvent" + } }], - "clientCount": 1, - "failoverEnabled": true, - "validateCertificates": true, - "processorPoolSize": 5, - "specificConfig": { - "parallelism": "1" - }, - "tags": [] + "specificConfig": { "parallelism": "1" } } ``` {%endraw%} -Parameters: -- ``: The ID of the log analytics workspace. -- ``: The primary or secondary shared key of the log analytics workspace. +### Azure IoT Hub (HTTP) -The connection publishes all fields of all twin events under the log type `TwinEvent`. After a maximum of 30 minutes -after the first event, Azure Monitor should have created a custom log type `TwinEvent_CL` containing a twin event -in each row. - -#### Azure IoT Hub HTTP API - -What follows is an example of a connection which forwards live messages sent to things as direct method calls to -[Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/about-iot-hub) via HTTP. You'll receive the response of the direct method call. +Forward live messages as direct method calls to +[Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/about-iot-hub): {%raw%} ```json @@ -384,55 +254,40 @@ What follows is an example of a connection which forwards live messages sent to "connectionType": "http-push", "connectionStatus": "open", "uri": "https://:443", + "credentials": { + "type": "hmac", + "algorithm": "az-sasl", + "parameters": { + "sharedKeyName": "", + "sharedKey": "", + "endpoint": "" + } + }, "sources": [], "targets": [{ "address": "POST:/twins/{{ thing:id }}/methods?api-version=2018-06-30", "topics": ["_/_/things/live/messages"], "authorizationContext": ["integration:ditto"], "issuedAcknowledgementLabel": "live-response", - "headerMapping": {}, "payloadMapping": ["javascript"] }], - "clientCount": 1, - "failoverEnabled": true, - "validateCertificates": true, - "processorPoolSize": 5, - "specificConfig": { - "parallelism": "1" - }, + "specificConfig": { "parallelism": "1" }, "mappingDefinitions": { "javascript": { "mappingEngine": "JavaScript", "options": { - "incomingScript": "function mapToDittoProtocolMsg() {\n return undefined;\n}\n", - "outgoingScript": "function mapFromDittoProtocolMsg(\n namespace,\n name,\n group,\n channel,\n criterion,\n action,\n path,\n dittoHeaders,\n value,\n status,\n extra\n) {\n\n let headers = dittoHeaders;\n let payload = {\n \"methodName\": action,\n \"responseTimeoutInSeconds\": parseInt(dittoHeaders.timeout),\n \"payload\": value\n };\n let textPayload = JSON.stringify(payload);\n let bytePayload = null;\n let contentType = 'application/json';\n\n return Ditto.buildExternalMsg(\n headers, // The external headers Object containing header values\n textPayload, // The external mapped String\n bytePayload, // The external mapped byte[]\n contentType // The returned Content-Type\n );\n}\n", - "loadBytebufferJS": "false", - "loadLongJS": "false" + "incomingScript": "function mapToDittoProtocolMsg() { return undefined; }", + "outgoingScript": "function mapFromDittoProtocolMsg(namespace,name,group,channel,criterion,action,path,dittoHeaders,value,status,extra) {\n let payload = { \"methodName\": action, \"responseTimeoutInSeconds\": parseInt(dittoHeaders.timeout), \"payload\": value };\n return Ditto.buildExternalMsg(dittoHeaders, JSON.stringify(payload), null, 'application/json');\n}" } } - }, - "credentials": { - "type": "hmac", - "algorithm": "az-sasl", - "parameters": { - "sharedKeyName": "", - "sharedKey": "", - "endpoint": "" - } - }, - "tags": [] + } } ``` {%endraw%} -Parameters: -* ``: The hostname of your iot hub instance. E.g. `my-hub.azure-devices.net`. -* ``: The name of the (Azure IoT Hub) shared access policy, which has the `Service connect` permission. E.g. `service`. -* ``: The primary or secondary key of above policy. E.g. `theKey`. - -The `outgoingScript` provides JavaScript payload mapping which maps the outgoing ditto protocol message into -the required [direct method format](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-direct-methods#invoke-a-direct-method-from-a-back-end-app). -I.e. it uses the live message subject as `methodName`, the timeout as `responseTimeoutInSeconds` and the value as `payload`: +The outgoing JavaScript mapper transforms the Ditto Protocol message into the +[direct method format](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-direct-methods#invoke-a-direct-method-from-a-back-end-app), +using the live message subject as `methodName`, the timeout as `responseTimeoutInSeconds`, and the value as `payload`: ```javascript function mapFromDittoProtocolMsg( @@ -468,7 +323,8 @@ function mapFromDittoProtocolMsg( } ``` -This is required since Azure IoT Hub direct methods require a specific format: +Azure IoT Hub direct methods require this JSON format: + ```json { "methodName": "reboot", @@ -480,12 +336,10 @@ This is required since Azure IoT Hub direct methods require a specific format: } ``` -Send live messages to a Thing, which contain as value the expected `payload`, as live message subject the expected -`methodName` and as timeout the expected `responseTimeoutInSeconds`. Then they can successfully invoke -the direct method on their respective devices. +Send live messages to a Thing containing the expected `payload` as value, the expected `methodName` as live message +subject, and the expected `responseTimeoutInSeconds` as timeout. For example, invoke the above direct method with a +`POST` to `/api/2/things//inbox/messages/reboot?timeout=200s` with body: -E.g. a live message for the above direct method would be a `POST` to -`/api/2/things//inbox/messages/reboot?timeout=200s` with body: ```json { "input1": "someInput", @@ -493,10 +347,9 @@ E.g. a live message for the above direct method would be a `POST` to } ``` -#### Azure IoT Hub AMQP API +### Azure IoT Hub (AMQP) -What follows is an example of a connections which forwards live messages sent to things as Cloud To Device (C2D) -messages to [Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/about-iot-hub) via AMQP 1.0. +Forward live messages as Cloud-to-Device messages via AMQP 1.0: {%raw%} ```json @@ -506,6 +359,15 @@ messages to [Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/about "connectionType": "amqp-10", "connectionStatus": "open", "uri": "amqps://:5671", + "credentials": { + "type": "hmac", + "algorithm": "az-sasl", + "parameters": { + "sharedKeyName": "", + "sharedKey": "", + "endpoint": "" + } + }, "sources": [], "targets": [{ "address": "/messages/devicebound", @@ -515,36 +377,14 @@ messages to [Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/about "iothub-ack": "full", "to": "/devices/{{thing:id}}/messages/devicebound" } - } - ], - "clientCount": 1, - "failoverEnabled": true, - "validateCertificates": true, - "processorPoolSize": 5, - "credentials": { - "type": "hmac", - "algorithm": "az-sasl", - "parameters": { - "sharedKeyName": "", - "sharedKey": "", - "endpoint": "" - } - }, - "tags": [] + }] } ``` {%endraw%} -Parameters: -* ``: The hostname of your iot hub instance. E.g. `my-hub.azure-devices.net`. -* ``: The name of the (Azure IoT Hub) shared access policy, which has the `Service connect` permission. E.g. `service`. -* ``: The primary or secondary key of above policy. E.g. `theKey`. +### Azure Service Bus (HTTP) - -#### Azure Service Bus HTTP API - -What follows is an example of a connection which forwards live messages sent to things to a queue in -[Azure Service Bus](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview). +Forward live messages to an [Azure Service Bus](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview) queue: {%raw%} ```json @@ -554,40 +394,71 @@ What follows is an example of a connection which forwards live messages sent to "connectionType": "http-push", "connectionStatus": "open", "uri": "https://:443", - "sources": [], - "targets": [{ - "address": "POST://messages", - "topics": ["_/_/things/live/messages"], - "authorizationContext": ["integration:ditto"], - "issuedAcknowledgementLabel": "live-response", - "headerMapping": {} - } - ], - "clientCount": 1, - "failoverEnabled": true, - "validateCertificates": true, - "processorPoolSize": 5, - "specificConfig": { - "parallelism": "1" - }, "credentials": { "type": "hmac", "algorithm": "az-sasl", "parameters": { - "sharedKeyName": "", - "sharedKey": "", + "sharedKeyName": "", + "sharedKey": "", "endpoint": "https:///" } }, - "tags": [] + "sources": [], + "targets": [{ + "address": "POST://messages", + "topics": ["_/_/things/live/messages"], + "authorizationContext": ["integration:ditto"], + "issuedAcknowledgementLabel": "live-response" + }], + "specificConfig": { "parallelism": "1" } } ``` {%endraw%} -Parameters: -* ``: The hostname of your service bus instance. E.g. `my-bus.servicebus.windows.net`. -* ``: The queue name to which you want to publish in your service bus instance. E.g. `my-queue`'. -* ``: The name of the (Azure Service Bus) shared access policy, which has `Send` and `Listen` permissions. E.g. `RootManageSharedAccessKey`. -* ``: The `Base64` encoded primary or secondary key of above policy. The signing work if you - encode the key with `Base64` (although it already has `Base64` encoding). E.g. if the primary key is `theKey`, you need to use - its encoded version `dGhlS2V5`. +For Azure Service Bus, the `sharedKey` must be additionally Base64-encoded (for example, `theKey` +becomes `dGhlS2V5`). + +## Custom algorithms + +You can add custom signing algorithms by implementing the appropriate interface and registering it +in [connectivity.conf](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf): + +| Connection type | Config path | Interface | +|----------------|-------------|-----------| +| HTTP Push | `ditto.connectivity.connection.http-push.hmac-algorithms` | [HttpRequestSigningFactory](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/messaging/httppush/HttpRequestSigningFactory.java) | +| AMQP 1.0 | `ditto.connectivity.connection.amqp10.hmac-algorithms` | [AmqpConnectionSigningFactory](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/messaging/amqp/AmqpConnectionSigningFactory.java) | + +The default configuration registers the pre-defined algorithms as follows: + +```hocon +ditto.connectivity.connection { + http-push.hmac-algorithms = { + + aws4-hmac-sha256 = + "org.eclipse.ditto.connectivity.service.messaging.httppush.AwsRequestSigningFactory" + + az-monitor-2016-04-01 = + "org.eclipse.ditto.connectivity.service.messaging.httppush.AzMonitorRequestSigningFactory" + + az-sasl = + "org.eclipse.ditto.connectivity.service.messaging.signing.AzSaslSigningFactory" + + // my-own-request-signing-algorithm = + // "my.package.MyOwnImplementationOfHttpRequestSigningFactory" + } + amqp10.hmac-algorithms = { + + az-sasl = + "org.eclipse.ditto.connectivity.service.messaging.signing.AzSaslSigningFactory" + + // my-own-connection-signing-algorithm = + // "my.package.MyOwnImplementationOfAmqpConnectionSigningFactory" + } +} +``` + +## Further reading + +* [HTTP 1.1 binding](connectivity-protocol-bindings-http.html) -- HTTP push connection details +* [AMQP 1.0 binding](connectivity-protocol-bindings-amqp10.html) -- AMQP 1.0 connection details +* [Connections overview](basic-connections.html) -- connection model and configuration diff --git a/documentation/src/main/resources/pages/ditto/connectivity-manage-connections-piggyback.md b/documentation/src/main/resources/pages/ditto/connectivity-manage-connections-piggyback.md index 117f0cd7a74..310f6f8c616 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-manage-connections-piggyback.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-manage-connections-piggyback.md @@ -1,17 +1,16 @@ --- title: Manage connections via Piggyback commands -keywords: +keywords: connectivity, connections, piggyback, manage tags: [connectivity] permalink: connectivity-manage-connections-piggyback.html --- -The recommended way of connection management (CRUD) is by using the [HTTP API to manage connections](connectivity-manage-connections.html). -Although not the recommended way, it's still possible to manage connections in Ditto -via DevOps [Piggyback commands](installation-operating.html#piggyback-commands) as well. +You manage connections primarily through the [HTTP API](connectivity-manage-connections.html). +Although not recommended, you can also manage connections via DevOps [Piggyback commands](installation-operating.html#piggyback-commands). All connection related piggyback commands use the following HTTP endpoint: -``` +```text POST /devops/piggyback/connectivity ``` @@ -270,7 +269,7 @@ successfully established. The only parameter necessary for retrieving the connec ### Retrieve connection metrics For details about the response of this command, please refer to -[Retrieve connection logs using HTTP API](connectivity-manage-connections.html#retrieve-connection-metrics). +[Retrieve connection logs using HTTP API](connectivity-manage-connections.html#connection-metrics). ```json { @@ -310,7 +309,7 @@ retrieving the connection metrics is the `connectionId`. ### Enable connection logs For details about the this command, please refer to -[Retrieve connection logs using HTTP API](connectivity-manage-connections.html#enable-connection-logs). +[Retrieve connection logs using HTTP API](connectivity-manage-connections.html#connection-logs). ```json { @@ -330,7 +329,7 @@ For details about the this command, please refer to ### Retrieve connection logs For details about the response of this command, please refer to -[Retrieve connection logs using HTTP API](connectivity-manage-connections.html#retrieve-connection-logs). +[Retrieve connection logs using HTTP API](connectivity-manage-connections.html#connection-logs). ```json { @@ -566,7 +565,7 @@ and progress will be saved to allow resuming later. ## Publishing connection logs -Please refer to [Payload mapping configuration](connectivity-manage-connections.html#publishing-connection-logs) in +Please refer to [Payload mapping configuration](connectivity-manage-connections.html#publishing-connection-logs-to-fluentd) in HTTP API section about managing connections. ## Payload mapping configuration diff --git a/documentation/src/main/resources/pages/ditto/connectivity-manage-connections.md b/documentation/src/main/resources/pages/ditto/connectivity-manage-connections.md index da222f593cb..1cad52bdec2 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-manage-connections.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-manage-connections.md @@ -1,272 +1,368 @@ --- -title: Manage connections via HTTP API +title: Managing Connections keywords: tags: [connectivity] permalink: connectivity-manage-connections.html --- -In order to manage (CRUD) connections in Ditto, the [HTTP API](http-api-doc.html#/Connections) -is recommended to be used instead of the old fashion [Manage connections via Piggyback commands](connectivity-manage-connections-piggyback.html). -As this is not a task for a developer, but more for a "DevOps engineer", the endpoint for creating new connections -to external systems is authenticated using the ["devops"](installation-operating.html#devops-user) user. +You create, modify, retrieve, and delete connections using the Ditto HTTP API or DevOps piggyback commands. + +{% include callout.html content="**TL;DR**: Use the HTTP API at `/api/2/connections` to manage connections. Authenticate as the `devops` user. You can also test connections with a dry run before persisting them." type="primary" %} + +## Overview + +Ditto provides two approaches to manage connections: + +1. **HTTP API** (recommended) -- RESTful endpoints under `/api/2/connections` +2. **Piggyback commands** (legacy) -- DevOps commands sent to the connectivity service + +Both approaches require [devops authentication](operating-devops.html#devops-user). ## Authorization -When creating new connections, an `authorizationContext` is needed providing the _authorization subjects_ (e.g. IDs of -authorized users) to use in order to determine the permissions for when the connection executes commands (e.g. modifying -a Thing). If you want to use a user for the basic auth (from the [HTTP API](connectivity-protocol-bindings-http.html)) -use the prefix `nginx:`, e.g. `nginx:ditto`. -See [Basic Authentication](basic-auth.html#authorization-context-in-devops-commands) for more information. +When you create a connection, you must specify an `authorizationContext` with authorization subjects +(for example, user IDs). These subjects determine the permissions applied when the connection +executes commands such as modifying a thing. -## Encryption sensitive configuration data -There is an option to enable encryption of credentials or other sensitive fields of the connection entities before they are -persisted in the database on global service configuration level. -For more details refer to [Connections sensitive data encryption](installation-operating.html#encrypt-sensitive-data-in-connections) +If you use basic auth from the [HTTP API](connectivity-protocol-bindings-http.html), prefix the +subject with `nginx:` (for example, `nginx:ditto`). +See [Basic Authentication](basic-auth.html#authorization-context-in-devops-commands) for details. -## CRUD endpoints +## Encrypting sensitive data -The following commands are available in order to manage connections: +You can enable encryption of credentials and other sensitive fields before they are persisted. +See [Connections sensitive data encryption](operating-configuration.html#encrypting-sensitive-connection-data) +for configuration details. -* [create](#create-connection) -* [modify](#modify-connection) -* [retrieve](#retrieve-connection) -* [delete](#delete-connection) +## Managing connections via HTTP API -### Create connection +### Create a connection -Create a new connection by sending an HTTP `POST` request: +Send an HTTP `POST` request: -`POST /api/2/connections` +``` +POST /api/2/connections +``` ```json { - "name": "", - "connectionType": "", - "connectionStatus": "", - "uri": "", - "sources": [], - "targets": [] + "name": "", + "connectionType": "", + "connectionStatus": "", + "uri": "", + "sources": [], + "targets": [] } ``` -The content of the connection configuration object is specified in the [Connections section](basic-connections.html). -For protocol specific examples, consult the specific connection type binding respectively: -* [AMQP-0.9.1 binding](connectivity-protocol-bindings-amqp091.html), -* [AMQP-1.0 binding](connectivity-protocol-bindings-amqp10.html), -* [MQTT-3.1.1 binding](connectivity-protocol-bindings-mqtt.html), -* [MQTT-5 binding](connectivity-protocol-bindings-mqtt5.html), -* [HTTP 1.1 binding](connectivity-protocol-bindings-http.html), -* [Apache Kafka 2.x binding](connectivity-protocol-bindings-kafka2.html) +The connection configuration format is described in the [Connections](basic-connections.html) section. +For protocol-specific examples, see the relevant binding page: +* [AMQP 0.9.1](connectivity-protocol-bindings-amqp091.html) +* [AMQP 1.0](connectivity-protocol-bindings-amqp10.html) +* [MQTT 3.1.1](connectivity-protocol-bindings-mqtt.html) +* [MQTT 5](connectivity-protocol-bindings-mqtt5.html) +* [HTTP 1.1](connectivity-protocol-bindings-http.html) +* [Kafka 2.x](connectivity-protocol-bindings-kafka2.html) -Additionally, you can test a connection before creating it: +#### Dry run -`POST /api/2/connections&dry-run=true` +Test a connection before persisting it: -Passing the `dry-run` query parameter checks the configuration and establishes a connection to the remote endpoint in order to validate the connection -credentials. The connection is closed afterwards and will not be persisted. +``` +POST /api/2/connections?dry-run=true +``` -### Modify connection +This validates the configuration and establishes a temporary connection to verify credentials. +The connection is closed afterward and not persisted. -Modify an existing connection by sending a HTTP `PUT` request to: +### Modify a connection -`PUT /api/2/connections/{connectionId}` +Send an HTTP `PUT` request: -```json -{ - "connectionStatus": "", - "...": "" -} +``` +PUT /api/2/connections/{connectionId} ``` -The connection with the specified ID needs to be created before one can modify it. - -### Retrieve connection +The connection must already exist before you can modify it. -The only parameter necessary for retrieving a connection is the `connectionId`: +### Retrieve a connection -`GET /api/2/connections/{connectionId}` +``` +GET /api/2/connections/{connectionId} +``` ### Retrieve all connections -Retrieves all created connections: -`GET /api/2/connections` - -Additionally, you can get the connections ids only by providing the `ids-only=true` query parameter. - -`GET /api/2/connections?ids-only=true` +``` +GET /api/2/connections +``` -### Delete connection +To retrieve only connection IDs: -The only parameter necessary for deleting a connection is the `connectionId`. +``` +GET /api/2/connections?ids-only=true +``` -`DELETE /api/2/connections/{connectionId}` +### Delete a connection -## Helper endpoints +``` +DELETE /api/2/connections/{connectionId} +``` -### Retrieve connection status +## Connection commands -Returns the connection status by showing if a connection is currently enabled/disabled and if it is -successfully established. The only parameter necessary for retrieving the connection status is the `connectionId`. +Send commands to a connection via: -`GET /api/2/connections/{connectionId}/status` +``` +POST /api/2/connections/{connectionId}/command +``` -### Retrieve connection metrics +Send the command as `text/plain` payload: -This command returns the connection metrics showing how many messages have been successfully or failingly `consumed`, -`filtered`, `mapped`, `published`, `dropped`. The metrics are collected and returned in different time intervals: -* the last minute -* the last hour -* the last 24 hours +| Command | Description | +|---------|-------------| +| `connectivity.commands:openConnection` | Opens the connection. Publishes a [ConnectionOpenedAnnouncement](protocol-specification-connections-announcement.html). | +| `connectivity.commands:closeConnection` | Closes the connection gracefully. Publishes a [ConnectionClosedAnnouncement](protocol-specification-connections-announcement.html). | +| `connectivity.commands:resetConnectionMetrics` | Resets all metrics to zero. | +| `connectivity.commands:enableConnectionLogs` | Enables logging for 24 hours. | +| `connectivity.commands:resetConnectionLogs` | Clears all stored connection logs. | -The only parameter necessary for retrieving the connection metrics is the `connectionId`. +## Monitoring connections -`GET /api/2/connections/{connectionId}/metrics` +### Connection status -### Retrieve connection logs +``` +GET /api/2/connections/{connectionId}/status +``` -Returns the connection logs. The only parameter necessary for retrieving the connection logs is the `connectionId`. +Shows whether a connection is enabled/disabled and whether it is successfully established. -This request will return a list of success and failure log entries containing information on messages processed by the -connection. The logs have a maximum amount of entries that they can hold. If the connection produces more log entries, -the older entries will be dropped. So keep in mind that you might miss some log entries. +### Connection metrics -The response will also provide information on how long the logging feature will still be enabled. Since the timer will -always be reset when retrieving the logs, the timestamp will always be 24 hours from now. +``` +GET /api/2/connections/{connectionId}/metrics +``` -The default duration and the maximum amount of logs stored for one connection can be configured in Ditto's connectivity -service configuration. +Returns counts of messages `consumed`, `filtered`, `mapped`, `published`, and `dropped` across +three time intervals: last minute, last hour, and last 24 hours. -`GET /api/2/connections/{connectionId}/logs` +### Connection logs -### Connection commands +``` +GET /api/2/connections/{connectionId}/logs +``` -The only parameter necessary for sending a command to a connection is the `connectionId`. +Returns success and failure log entries for messages processed by the connection. Logs have a +maximum capacity; older entries are dropped when the limit is reached. -`POST /api/2/connections/{connectionId}/command` +{% include note.html content="When creating or opening a connection the logging is enabled per default. This allows +to log possible errors on connection establishing." %} -Supported commands sent as payload `text/plain`: +## Publishing connection logs to Fluentd -#### Open connection +In addition to in-memory logs, you can publish connection logs to a +[Fluentd](https://www.fluentd.org) or [Fluent Bit](https://fluentbit.io) endpoint. -When opening a connection a [ConnectionOpenedAnnouncement](protocol-specification-connections-announcement.html) will be published. +Configure this via environment variables in the [connectivity](architecture-services-connectivity.html) service: -``` -connectivity.commands:openConnection -``` +| Variable | Description | Default | +|----------|-------------|---------| +| `CONNECTIVITY_LOGGER_PUBLISHER_ENABLED` | Enable log publishing | `false` | +| `CONNECTIVITY_LOGGER_PUBLISHER_LOG_TAG` | Log tag (uses `connection:` if unset) | -- | +| `CONNECTIVITY_LOGGER_PUBLISHER_FLUENCY_HOST` | Fluentd/Fluent Bit hostname | `localhost` | +| `CONNECTIVITY_LOGGER_PUBLISHER_FLUENCY_PORT` | Fluentd/Fluent Bit port | `24224` | -#### Close connection +Each log entry contains: -When gracefully closing a connection a [ConnectionClosedAnnouncement](protocol-specification-connections-announcement.html) will be published. +| Field | Values | +|-------|--------| +| `level` | `success`, `failure` | +| `category` | `connection`, `source`, `target`, `response` | +| `type` | `consumed`, `dispatched`, `filtered`, `mapped`, `dropped`, `enforced`, `published`, `acknowledged` | +| `connectionId` | ID of the connection which produced the log entry | +| `correlationId` | Correlation ID (if available) | +| `address` | Source/target address (if available) | +| `entityId` | Thing or policy ID | +| `entityType` | `"thing"` or `"policy"` | +| `instanceId` | ID of the connectivity instance (helpful when `clientCount > 1`) | +| `message` | The actual log message | -``` -connectivity.commands:closeConnection -``` +See [connectivity.conf](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf) +at path `ditto.connectivity.monitoring.logger.publisher` for all options. -#### Reset connection metrics +## Payload mapping configuration -This command resets the connection metrics - all metrics are set to `0` again. +To use a custom [payload mapping](connectivity-mapping.html) for a source or target, define a +`mappingDefinitions` entry in the connection and reference it by ID: -``` -connectivity.commands:resetConnectionMetrics +```json +{ + "mappingDefinitions": { + "customJs": { + "mappingEngine": "JavaScript", + "options": { + "incomingScript": "..", + "outgoingScript": ".." + } + } + }, + "sources": [{ + "addresses": "source", + "payloadMapping": ["customJs"] + }] +} ``` -#### Enable connection logs +If no mapping is specified, the [Ditto mapper](connectivity-mapping.html#ditto-mapper) is used by default. -Enables the connection logging feature of a connection for 24 hours. As soon as connection logging is enabled, you will -be able to [retrieve connection logs](#retrieve-connection-logs). The logs will contain a fixed amount of success and -failure logs for each source and target of your connection and correlate with the metrics of the connection. This will -allow you more insight in what goes well, and more importantly, what goes wrong. +## Managing connections via piggyback commands -The default duration and the maximum amount of logs stored for one connection can be configured in Ditto's connectivity -service configuration. +Although the HTTP API is recommended, you can still manage connections via DevOps +[piggyback commands](operating-devops.html#piggyback-commands). -{% include note.html content="When creating or opening a connection the logging is enabled per default. This allows -to log possible errors on connection establishing." %} +All piggyback commands use this endpoint: ``` -connectivity.commands:enableConnectionLogs +POST /devops/piggyback/connectivity ``` -#### Reset connection logs +### Create connection (piggyback) -Clears all currently stored connection logs. +```json +{ + "targetActorSelection": "/system/sharding/connection", + "headers": { + "aggregate": false, + "is-group-topic": false + }, + "piggybackCommand": { + "type": "connectivity.commands:createConnection", + "connection": {} + } +} +``` +### Modify connection (piggyback) + +```json +{ + "targetActorSelection": "/system/sharding/connection", + "headers": { + "aggregate": false, + "is-group-topic": false, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "connectivity.commands:modifyConnection", + "connection": {} + } +} ``` -connectivity.commands:resetConnectionLogs + +### Retrieve connection (piggyback) + +```json +{ + "targetActorSelection": "/system/sharding/connection", + "headers": { + "aggregate": false, + "is-group-topic": false, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "connectivity.commands:retrieveConnection", + "connectionId": "" + } +} ``` -## Publishing connection logs +### Open connection (piggyback) -In addition to [enable collecting in-memory connection logs](#enable-connection-logs), connection logs may also be -published to a [Fluentd](https://www.fluentd.org) or [Fluent Bit](https://fluentbit.io) endpoint from where they can be -forwarded into a logging backend of your choice. +```json +{ + "targetActorSelection": "/system/sharding/connection", + "headers": { + "aggregate": false, + "is-group-topic": false, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "connectivity.commands:openConnection", + "connectionId": "" + } +} +``` -This publishing of connection logs is not configured dynamically via a [piggyback helper endpoint](#helper-endpoints), -instead this must be enabled via [configuration](installation-operating.html#ditto-configuration) in the -`connectivity.conf` by setting environment variables or overwriting the configuration via system properties. +### Close connection (piggyback) -For example, set the following environment variables in the [connectivity](architecture-services-connectivity.html) -service: -* `CONNECTIVITY_LOGGER_PUBLISHER_ENABLED` - set to `true` in order to enable connection log publishing, default: `false` -* `CONNECTIVITY_LOGGER_PUBLISHER_LOG_TAG` - to specify a log tag to use for the published log entries - - if this is not set, the default `connection:` will be used and the specific connection-id will be injected -* `CONNECTIVITY_LOGGER_PUBLISHER_FLUENCY_HOST` - the hostname of the Fluentd or Fluent Bit endpoint, default: `"localhost"` -* `CONNECTIVITY_LOGGER_PUBLISHER_FLUENCY_PORT` - the port of the Fluentd or Fluent Bit endpoint, default `24224` +```json +{ + "targetActorSelection": "/system/sharding/connection", + "headers": { + "aggregate": false, + "is-group-topic": false, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "connectivity.commands:closeConnection", + "connectionId": "" + } +} +``` -The contained fields in a single log entry are the following: -* `connectionId`: ID of the connection which produced the log entry -* `level`: one of: `"success"|"failure"` -* `category`: one of: `"connection"|"source"|"target"|"response"` -* `type`: one of: `"consumed"|"dispatched"|"filtered"|"mapped"|"dropped"|"enforced"|"published"|"acknowledged"` -* `correlationId`: correlationId of the command/event, if available -* `address`: address of the Source/Target (e.g. MQTT topic, HTTP path), if available -* `entityType` : one of: `"thing"|"policy"` -* `entityId`: ID of the entity for which e.g. an event/message was processed (e.g. the Thing ID) -* `instanceId`: ID of the connectivity instance which processed the command/event, helpful if clientCount > 1 was configured -* `message`: the actual log message +### Delete connection (piggyback) -Please inspect the other available configuration options in -[connectivity.conf](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf) -at path `ditto.connectivity.monitoring.logger.publisher` to learn about other configuration possibilities. +```json +{ + "targetActorSelection": "/system/sharding/connection", + "headers": { + "aggregate": false, + "is-group-topic": false, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "connectivity.commands:deleteConnection", + "connectionId": "" + } +} +``` +### Test connection (piggyback) -## Payload mapping configuration +```json +{ + "targetActorSelection": "/system/sharding/connection", + "headers": { + "aggregate": false, + "is-group-topic": false, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "connectivity.commands:testConnection", + "connection": {} + } +} +``` -To enable a custom [payload mapping](connectivity-mapping.html) for a specific source or target of a connection, you -have to configure a payload mapping definition in the connection configuration object. The following snippet shows an -example `mappingDefinitions`. This configuration must be embedded in the connection configuration as shown in the -[Connections](basic-connections.html) section. These payload mapping definitions are then referenced by its ID -(the key of the JSON object) in the sources and targets of the connection using the field `payloadMapping`. If no -payload mapping or definition is provided, the [Ditto message mapping](connectivity-mapping.html#ditto-mapper) -is used as the default. +### Retrieve all connection IDs (piggyback) ```json { - ... - "mappingDefinitions": { - "customJs": { - // (1) - "mappingEngine": "JavaScript", - // (2) - "options": { - // (3) - "incomingScript": "..", - "outgoingScript": ".." - } - } + "targetActorSelection": "/user/connectivityRoot/connectionIdsRetrieval", + "headers": { + "aggregate": false, + "is-group-topic": false, + "ditto-sudo": true }, - "sources": [ - { - "addresses": "source", - "payloadMapping": [ - "customJs" - ] - } - ] - ... + "piggybackCommand": { + "type": "connectivity.commands:retrieveAllConnectionIds" + } } ``` -- (1) This ID can be used in sources and targets of the connection to reference this payload mapping definition. -- (2) The `mappingEngine` defines the underlying `MessageMapper` implementation. -- (3) The `options` are used to configure the mapper instance to your needs. +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Payload mapping](connectivity-mapping.html) -- transform message payloads +* [Header mapping](connectivity-header-mapping.html) -- map external headers +* [Installation and operation](installation-operating.html) -- DevOps configuration diff --git a/documentation/src/main/resources/pages/ditto/connectivity-mapping.md b/documentation/src/main/resources/pages/ditto/connectivity-mapping.md index 8f3aa1bb387..1b280dbef6c 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-mapping.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-mapping.md @@ -1,151 +1,104 @@ --- -title: Payload mapping in connectivity service +title: Payload Mapping keywords: mapping, transformation, payload, javascript, mapper, protobuf tags: [connectivity] permalink: connectivity-mapping.html --- -{% include callout.html content="**TL;DR**
    The payload mapping feature in Ditto's connectivity APIs can be used to - transform arbitrary payload consumed via the different supported protocols - to [Ditto Protocol](protocol-overview.html) messages and vice versa." type="primary" %} +You use payload mapping to transform messages between your device's native format and [Ditto Protocol](protocol-overview.html) JSON. +{% include callout.html content="**TL;DR**: Payload mapping transforms arbitrary payloads consumed via connections + to [Ditto Protocol](protocol-overview.html) messages and vice versa. Use built-in mappers or write custom JavaScript to handle any format." type="primary" %} -## Motivation +## Overview -Eclipse Ditto is about providing access to IoT devices via the [digital twin](intro-digitaltwins.html) pattern. -In order to provide structured APIs for different heterogeneous devices Ditto defines a lightweight JSON based -[model](basic-overview.html). - -A [Thing](basic-thing.html) might look like in the following example: - -```json -{ - "thingId": "the.namespace:the-thing-id", - "policyId": "the.namespace:the-policy-id", - "attributes": { - "location": "kitchen" - }, - "features": { - "transmission": { - "properties": { - "cur_speed": 90 - } - } - } -} -``` - -Devices in the IoT, may they be brownfield devices or newly produced devices, will probably not send their data to the -cloud in the structure and [protocol](protocol-overview.html) Ditto requires. - -They should not need to be aware of something like Ditto running in the cloud mirroring them as digital twins. - -So for example device payload could look like this: +Devices rarely send data in Ditto Protocol format. A device might send: ```json -{ - "val": "23.42 °C", - "ts": 1523946112727 -} +{"val": "23.42 °C", "ts": 1523946112727} ``` -In case of constrained devices or IoT protocols, even binary payload might be common. +Or even binary: ``` -0x08BD (hex representation) +0x08BD ``` -## Builtin mappers +Payload mapping bridges this gap by converting between device-native formats and the structured +[Ditto Protocol](protocol-specification.html) that Ditto requires. -The following message mappers are included in the Ditto codebase: +## Built-in mappers -| Mapper Alias | Description | Inbound | Outbound | -|------------|--------------------------------|---------------------------|---------------------------| -| [Ditto](#ditto-mapper) | Assumes that inbound/outbound messages are already in [Ditto Protocol](protocol-overview.html) (JSON) format. | ✓ | ✓ | -| [JavaScript](#javascript-mapper) | Converts arbitrary messages from and to the [Ditto Protocol](protocol-overview.html) format using **custom** JavaScript code executed by Ditto. | ✓ | ✓ | -| [Normalized](#normalized-mapper) | Transforms the payload of events to a normalized view. | | ✓ | -| [ConnectionStatus](#connectionstatus-mapper) | This mapper handles messages containing `creation-time` and `ttd` headers by updating a feature of the targeted thing with [definition](basic-feature.html#feature-definition) [ConnectionStatus](https://github.com/eclipse/vorto/tree/development/models/org.eclipse.ditto-ConnectionStatus-1.0.0.fbmodel). | ✓ | | -| [RawMessage](#rawmessage-mapper) | For outgoing message commands and responses, this mapper extracts the payload for publishing directly into the channel. For incoming messages, this mapper wraps them in a configured message command or response envelope. | ✓ | ✓ | -| [ImplicitThingCreation](#implicitthingcreation-mapper) | This mapper handles messages for which a Thing should be created automatically based on a defined template. | ✓ | | -| [UpdateTwinWithLiveResponse](#updatetwinwithliveresponse-mapper) | This mapper creates a [merge Thing command](protocol-specification-things-merge.html) when an indiviudal [retrieve command](protocol-specification-things-retrieve.html) for an single Thing was received via the [live channel](protocol-twinlive.html#live) patching exactly the retrieved "live" data into the twin. | ✓ | | -| [CloudEvents Mapper](#cloudevents-mapper) | The mapper maps incoming CloudEvent to Ditto Protocol. Supports both Binary and Structured CloudEvent. | ✓ | ✓ | +| Mapper | Alias | Inbound | Outbound | Description | +|--------|-------|:---:|:---:|-------------| +| [Ditto](#ditto-mapper) | `Ditto` | ✔ | ✔ | Messages already in Ditto Protocol format | +| [JavaScript](#javascript-mapper) | `JavaScript` | ✔ | ✔ | Custom JS scripts for arbitrary formats | +| [Normalized](#normalized-mapper) | `Normalized` | | ✔ | Transforms events to a normalized JSON view | +| [ConnectionStatus](#connectionstatus-mapper) | `ConnectionStatus` | ✔ | | Updates a feature based on `ttd`/`creation-time` headers | +| [RawMessage](#rawmessage-mapper) | `RawMessage` | ✔ | ✔ | Maps message command payloads directly | +| [ImplicitThingCreation](#implicitthingcreation-mapper) | `ImplicitThingCreation` | ✔ | | Auto-creates things from incoming messages | +| [UpdateTwinWithLiveResponse](#updatetwinwithliveresponse-mapper) | `UpdateTwinWithLiveResponse` | ✔ | | Patches twin data from live responses | +| [CloudEvents](#cloudevents-mapper) | `CloudEvents` | ✔ | ✔ | Maps CloudEvent format to Ditto Protocol | ### Ditto mapper -This is the default [Ditto Protocol](protocol-overview.html) mapper. If you do not specify any payload mapping this - mapper is used to map inbound and outbound messages. The mapper requires no mandatory options, so its alias can - be directly used as a mapper reference. - -It assumes that received messages are in [Ditto Protocol JSON](protocol-specification.html) and emits outgoing messages - also in that format. +The default mapper. Assumes messages are in [Ditto Protocol JSON](protocol-specification.html). +No configuration required -- use the alias `Ditto` directly. ### JavaScript mapper -This mapper may be used whenever any inbound messages are not yet in [Ditto Protocol](protocol-overview.html). -By using the built in [JavaScript mapping engine](#javascript-mapping-engine) (based on Rhino) custom defined -JavaScript scripts can be executed which are responsible for creating [Ditto Protocol JSON](protocol-specification.html) -message from arbitrary consumed payload. +Transforms arbitrary payloads using custom JavaScript scripts executed in a sandboxed +[Rhino](https://github.com/mozilla/rhino) engine. See the +[JavaScript mapping engine](#javascript-mapping-engine) section for details. -The same is possible for outbound messages in order to transform [Ditto Protocol JSON](protocol-specification.html) -messages (e.g. events or responses) to arbitrary other formats. +**Options:** -#### Configuration options - -* `incomingScript` (required): the mapping script for incoming messages -* `outgoingScript` (required): the mapping script for outgoing messages -* `loadBytebufferJS` (optional, default: `"false"`): whether to load ByteBufferJS library -* `loadLongJS` (optional, default: `"false"`): whether to load LongJS library +| Option | Required | Description | +|--------|----------|-------------| +| `incomingScript` | Yes | Script for inbound messages | +| `outgoingScript` | Yes | Script for outbound messages | +| `loadBytebufferJS` | No | Load ByteBufferJS library (default: `false`) | +| `loadLongJS` | No | Load LongJS library (default: `false`) | ### Normalized mapper -This mapper transforms `created`, `modified`, and `deleted` events (other type of messages are dropped) to a normalized view. -Events are mapped to a nested sparse JSON. - -**Note:** By default, only complete thing deletions (`ThingDeleted`) are mapped with a special `_deleted` field. Partial -deletions like `AttributeDeleted`, `FeatureDeleted`, etc. are dropped unless explicitly enabled (see option below). +Transforms `created`, `modified`, and `deleted` events to a normalized JSON structure. +Other message types are dropped. -#### Configuration options +**Options:** -* `includeDeletedFields` (optional, default: `false`): when enabled, partial delete events are mapped and merge-patch - `null` values are tracked in `_deletedFields`. The `_deletedFields` object mirrors the JSON structure of the deleted - paths and stores ISO-8601 timestamps at the leaf nodes. +| Option | Default | Description | +|--------|---------|-------------| +| `fields` | all | Comma-separated list of [field selectors](httpapi-concepts.html#field-selectors) | +| `includeDeletedFields` | `false` | Track partial deletions in `_deletedFields` | +Example input: ```json { "topic": "thing/id/things/twin/events/modified", - "headers": { "content-type": "application/json" }, - "path": "/features/sensors/properties/temperature/indoor/value", + "path": "/features/sensors/properties/temperature/value", "value": 42 } ``` -would result in the following normalized JSON representation: - +Normalized output: ```json { "thingId": "thing:id", "features": { "sensors": { "properties": { - "temperature": { - "indoor": { - "value": 42 - } - } + "temperature": { "value": 42 } } } }, "_context": { "topic": "thing/id/things/twin/events/modified", - "path": "/features/sensors/properties/temperature/indoor/value", - "value": 42, - "headers": { - "content-type": "application/json" - } + "path": "/features/sensors/properties/temperature/value", + "value": 42 } } ``` -The `_context` field contains the original message content excluding the `value`. For `deleted` events, the mapper includes a `_deleted` field with the deletion timestamp: @@ -158,7 +111,7 @@ For `deleted` events, the mapper includes a `_deleted` field with the deletion t } ``` -would result in the following normalized JSON representation: +Normalized output: ```json { @@ -175,11 +128,9 @@ would result in the following normalized JSON representation: } ``` -The `_deleted` field contains the timestamp when the thing was deleted in ISO-8601 format. - -**Note:** The `_deleted` field is only added for complete thing deletions (when the entire thing is deleted). +The `_deleted` field contains the ISO-8601 timestamp when the thing was deleted. This field is only added for complete thing deletions. -When `includeDeletedFields` is enabled, the mapper adds `_deletedFields` for partial deletions and merge-patch deletions: +When `includeDeletedFields` is enabled, partial deletions and merge-patch deletions are tracked in `_deletedFields`. The `_deletedFields` object mirrors the JSON structure of the deleted paths and stores ISO-8601 timestamps at the leaf nodes: ```json { @@ -197,21 +148,24 @@ When `includeDeletedFields` is enabled, the mapper adds `_deletedFields` for par } ``` -#### Configuration options - -* `fields` (optional, default: all fields): comma separated list of fields that are contained in the result (see also - chapter about [field selectors](httpapi-concepts.html#with-field-selector)) - ### ConnectionStatus mapper -This mapper transforms the information from the `ttd` and `creation-time` message headers -(see Eclipse Hono [device notifications](https://www.eclipse.org/hono/docs/concepts/device-notifications/)) into a -ModifyFeature command that complies with the [Vorto functionblock](https://github.com/eclipse/vorto/tree/development/models/org.eclipse.ditto-ConnectionStatus-1.0.0.fbmodel) `{%raw%}org.eclipse.ditto:ConnectionStatus{%endraw%}`. - -The connectivity state of the device is then represented in a Feature.
    -It is mostly used in conjunction with another mapper that transforms the payload e.g.:
    -`"payloadMapping": [ "Ditto" , "connectionStatus" ]` - + +Transforms `ttd` and `creation-time` headers (from +[Eclipse Hono device notifications](https://www.eclipse.org/hono/docs/concepts/device-notifications/)) +into a ModifyFeature command that updates a `ConnectionStatus` feature. + +Typically used alongside another mapper: +`"payloadMapping": ["Ditto", "connectionStatus"]` + +**Options:** + +| Option | Required | Description | +|--------|----------|-------------| +| `thingId` | Yes | Thing ID (supports placeholders like `{%raw%}{{ header:device_id }}{%endraw%}`) | +| `featureId` | No | Feature ID (default: `ConnectionStatus`) | + Example of a resulting `ConnectionStatus` feature: + ```json { "thingId": "eclipse:ditto", @@ -228,25 +182,47 @@ Example of a resulting `ConnectionStatus` feature: } } ``` - -#### Configuration options -* `thingId` (required): The ID of the Thing that is updated with the connectivity state. It can either be a fixed value - or a header placeholder (e.g. `{%raw%}{{ header:device_id }}{%endraw%}`). -* `featureId` (optional, default: `ConnectionStatus`): The ID of the Feature that is updated. It can either be a - fixed value or resolved from a message header (e.g. `{%raw%}{{ header:feature_id }}{%endraw%}`). +Use the ConnectionStatus mapper alongside another mapper in a source configuration: + +```json +{ + "addresses": [""], + "authorizationContext": ["ditto:inbound"], + "payloadMapping": ["Ditto", "connectionStatus"] +} +``` ### RawMessage mapper -This mapper relates the payload in the `"value"` field of message commands and message responses to the payload -of AMQP, MQTT and Kafka messages and the body of HTTP requests. The encoding of the payload is chosen according to -the configured content type. The subject, direction, thing ID and feature ID of the envelope for incoming message -commands and responses need to be configured. +Maps message command/response payloads directly to/from the external message format. The encoding +is determined by the content type. + +For incoming messages, the mapper wraps the payload in a message command envelope. +For outgoing messages, the mapper extracts the `"value"` field for publishing. + +Messages with `content-type: application/vnd.eclipse.ditto+json` fall through to the Ditto mapper. -Messages with the Ditto protocol content type `application/vnd.eclipse.ditto+json` or signals that are not message -commands or responses are mapped by the [Ditto mapper](#ditto-mapper) instead. +**Options:** + +| Option | Default | Description | +|--------|---------|-------------| +| `outgoingContentType` | `text/plain; charset=UTF-8` | Fallback content type for outgoing messages | +| `incomingMessageHeaders` | (see below) | Headers for constructing the message envelope | + +Key incoming headers (all support placeholders): + +| Header | Purpose | Default | +|--------|---------|---------| +| `content-type` | Encoding of the payload | `{%raw%}{{ header:content-type \| fn:default('application/octet-stream') }}{%endraw%}` | +| `ditto-message-subject` | Message subject (required for MQTT 3) | `{%raw%}{{ header:ditto-message-subject }}{%endraw%}` | +| `ditto-message-thing-id` | Target thing ID (required for MQTT 3) | `{%raw%}{{ header:ditto-message-thing-id }}{%endraw%}` | +| `ditto-message-direction` | `TO` (inbox) or `FROM` (outbox) | `TO` | +| `ditto-message-feature-id` | Feature ID (omit for thing-level messages) | `{%raw%}{{ header:ditto-message-feature-id }}{%endraw%}` | +| `status` | Include for responses, omit for commands | `{%raw%}{{ header:status }}{%endraw%}` | + +The mapper maps between a feature message command response like: -For example, the mapper maps between the feature message command response ```json { "topic": "org.eclipse.ditto/smartcoffee/things/live/messages/heatUp", @@ -256,11 +232,15 @@ For example, the mapper maps between the feature message command response "status": 200 } ``` -and an AMQP, MQTT 5, Kafka message with payload or an HTTP request with body of 6 bytes + +and an AMQP, MQTT 5, or Kafka message with payload of 6 bytes: + ``` 0x01 02 03 04 05 06 ``` -and headers + +with headers: + ``` content-type: application/octet-stream status: 200 @@ -269,11 +249,11 @@ ditto-message-direction: TO ditto-message-thing-id: org.eclipse.ditto:smartcoffee ditto-message-feature-id: water-tank ``` -The headers are lost for connection protocols without application headers such as MQTT 3. - -#### Configuration options + +Headers are lost for connection protocols without application headers such as MQTT 3. Example configuration: + ```json { "outgoingContentType": "application/octet-stream", @@ -288,77 +268,43 @@ Example configuration: } ``` -* `outgoingContentType` (optional): The fallback content-type for outgoing message commands and responses without - the content-type header. Default to `text/plain; charset=UTF-8`. -* `incomingMessageHeaders` (optional): A JSON object containing the following headers needed to construct a message - command or response envelope containing the incoming message as payload in the field `"value"`. - The following placeholders may be used in the headers: - - | Placeholder | Description | - |-----------------------------------|--------------| - | `{%raw%}{{ header: }}{%endraw%}` | header value from the external message, e.g. from protocol headers | - | `{%raw%}{{ request:subjectId }}{%endraw%}` | the first authenticated subjectId which did the request - the one of the connection source in this case | - | `{%raw%}{{ time:now }}{%endraw%}` | the current timestamp in ISO-8601 format as string in UTC timezone | - | `{%raw%}{{ time:now_epoch_millis }}{%endraw%}` | the current timestamp in "milliseconds since epoch" formatted as string | - - * `content-type` (optional): The content type with which to encode the incoming message as payload. - Default to `{%raw%}{{ header:content-type | fn:default('application/octet-stream') }}{%endraw%}`. - If resolved to the Ditto protocol content type `application/vnd.eclipse.ditto+json`, then the entire payload - is interpreted as a Ditto protocol message instead. - * `status` (optional): Include for message responses. Exclude for message commands. Default to - `{%raw%}{{ header:status }}{%endraw%}`. - * `ditto-message-subject` (mandatory for MQTT 3): Subject of the message. Default to `{%raw%}{{ header:ditto-message-subject }}{%endraw%}`. - Mapping will fail if not resolvable. - * `ditto-message-direction` (optional): The message direction. Default to `TO`, which corresponds to `inbox` in - message commands and responses. - * `ditto-message-thing-id` (mandatory for MQTT 3): ID of the thing to send the message command or response to. - Default to `{%raw%}{{ header:ditto-message-thing-id }}{%endraw%}`. Mapping will fail if not resolvable. - * `ditto-message-feature-id` (optional): Include to send the message or message response to a feature of the thing. - Exclude to send it to the thing itself. Default to `{%raw%}{{ header:ditto-message-feature-id }}{%endraw%}`. - ### ImplicitThingCreation mapper -This mapper implicitly creates a new thing for an incoming message. - -The created thing contains the values defined in the template, configured in the `mappingDefinitions` `options`. +Automatically creates a thing when an incoming message arrives. The thing structure is defined +in the `thing` option as a JSON template with placeholder support. -#### Configuration options +**Options:** -* `thing` (required): The values of the thing that is created implicitly. It can either contain fixed values - or header placeholders (e.g. `{%raw%}{{ header:device_id }}{%endraw%}`). - * the following placeholders may be used inside the `"thing"` JSON: +* `thing` (required): The values of the thing that is created implicitly. It can contain fixed values + or header placeholders (e.g. `{%raw%}{{ header:device_id }}{%endraw%}`). + * The following placeholders may be used inside the `"thing"` JSON: - | Placeholder | Description | - |------------------------------------|--------------| - | `{%raw%}{{ header: }}{%endraw%}` | header value from the external message, e.g. from protocol headers | - | `{%raw%}{{ request:subjectId }}{%endraw%}` | the first authenticated subjectId which did the request - the one of the connection source in this case | - | `{%raw%}{{ time:now }}{%endraw%}` | the current timestamp in ISO-8601 format as string in UTC timezone | - | `{%raw%}{{ time:now_epoch_millis }}{%endraw%}` | the current timestamp in "milliseconds since epoch" formatted as string | + | Placeholder | Description | + |-------------|-------------| + | `{%raw%}{{ header: }}{%endraw%}` | Header value from the external message, e.g. from protocol headers | + | `{%raw%}{{ request:subjectId }}{%endraw%}` | The first authenticated subjectId which did the request | + | `{%raw%}{{ time:now }}{%endraw%}` | The current timestamp in ISO-8601 format as string in UTC timezone | + | `{%raw%}{{ time:now_epoch_millis }}{%endraw%}` | The current timestamp in "milliseconds since epoch" formatted as string | * The `"thing"` JSON may also include: - * an inline policy: `"_policy"` containing the [Policy JSON](basic-policy.html#model-specification) to create a new policy - from and link with the thing + * an inline policy: `"_policy"` containing the [Policy JSON](basic-policy.html#model-specification) * a "copy policy from" statement: `"_copyPolicyFrom"` - see also [create Thing alternatives](protocol-examples-creatething.html#alternative-creatething-commands) * either including a policyId to copy from - * or containing the link to a thing to copy the policy from in the form: `{% raw %}{{ ref:things//policyId }}{% endraw %}` - -* `commandHeaders` (optional, default: `{"If-None-Match": "*"}`): The Ditto headers to use for constructing the "create thing" command for creating the - twin and to use for creating errors. - * in this configured headers, the following placeholders may be used: - - | Placeholder | Description | - |-----------------------------------|--------------| - | `{%raw%}{{ header: }}{%endraw%}` | header value from the external message, e.g. from protocol headers | - | `{%raw%}{{ request:subjectId }}{%endraw%}` | the first authenticated subjectId which did the request - the one of the connection source in this case | - | `{%raw%}{{ time:now }}{%endraw%}` | the current timestamp in ISO-8601 format as string in UTC timezone | - | `{%raw%}{{ time:now_epoch_millis }}{%endraw%}` | the current timestamp in "milliseconds since epoch" formatted as string | - -* `allowPolicyLockout` (optional, default: `true`): whether it should be allowed to create policies without having `WRITE` - permissions in the created policy for the subject which creates the policy - (the [authorizationContext](connectivity-manage-connections.html#authorization) of the connection source which - received the message for which a thing should be created implicitly) - -Example of a template defined in `options`: + * or containing the link to a thing to copy the policy from in the form: `{%raw%}{{ ref:things//policyId }}{%endraw%}` + +* `commandHeaders` (optional, default: `{"If-None-Match": "*"}`): The Ditto headers to use for constructing the "create thing" command. + * The following placeholders may be used: + + | Placeholder | Description | + |-------------|-------------| + | `{%raw%}{{ header: }}{%endraw%}` | Header value from the external message, e.g. from protocol headers | + | `{%raw%}{{ request:subjectId }}{%endraw%}` | The first authenticated subjectId which did the request | + | `{%raw%}{{ time:now }}{%endraw%}` | The current timestamp in ISO-8601 format as string in UTC timezone | + | `{%raw%}{{ time:now_epoch_millis }}{%endraw%}` | The current timestamp in "milliseconds since epoch" formatted as string | + +* `allowPolicyLockout` (optional, default: `true`): Whether it should be allowed to create policies without having `WRITE` + permissions in the created policy for the subject which creates the policy. + ```json { "thing": { @@ -377,26 +323,25 @@ Example of a template defined in `options`: ### UpdateTwinWithLiveResponse mapper -This mapper creates a [merge Thing command](protocol-specification-things-merge.html) when a -[retrieve command](protocol-specification-things-retrieve.html) was received via the -[live channel](protocol-twinlive.html#live) patching exactly the retrieved "live" data into the twin. - -#### Configuration options +Creates a [merge Thing command](protocol-specification-things-merge.html) from a +[live retrieve response](protocol-twinlive.html#live-channel), patching the live data into the twin. -* `dittoHeadersForMerge` (optional): The Ditto headers to use for constructing the "merge thing" - command for updating the twin, may for example add a condition to apply in order to update the twin - (default applied Ditto headers if not configured: `"response-required": false`, `"if-match": "*"`). - * in this configured headers, the following placeholders may be used: +**Options:** - | Placeholder | Description | - |-----------------------------------|--------------| - | `{%raw%}{{ header: }}{%endraw%}` | header value from the external message, e.g. from protocol headers | - | `{%raw%}{{ request:subjectId }}{%endraw%}` | the first authenticated subjectId which did the request - the one of the connection source in this case | - | `{%raw%}{{ time:now }}{%endraw%}` | the current timestamp in ISO-8601 format as string in UTC timezone | - | `{%raw%}{{ time:now_epoch_millis }}{%endraw%}` | the current timestamp in "milliseconds since epoch" formatted as string | +* `dittoHeadersForMerge` (optional): The Ditto headers to use for constructing the "merge thing" + command, may for example add a condition to apply in order to update the twin + (default: `"response-required": false`, `"if-match": "*"`). + * The following placeholders may be used: + | Placeholder | Description | + |-------------|-------------| + | `{%raw%}{{ header: }}{%endraw%}` | Header value from the external message, e.g. from protocol headers | + | `{%raw%}{{ request:subjectId }}{%endraw%}` | The first authenticated subjectId which did the request | + | `{%raw%}{{ time:now }}{%endraw%}` | The current timestamp in ISO-8601 format as string in UTC timezone | + | `{%raw%}{{ time:now_epoch_millis }}{%endraw%}` | The current timestamp in "milliseconds since epoch" formatted as string | Example configuration: + ```json { "dittoHeadersForMerge": { @@ -411,11 +356,13 @@ Example configuration: } } ``` -### CloudEvents Mapper -This mapper maps incoming [CloudEvent](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md) to Ditto Protocol. It provides support for both Binary CloudEvents as well as Structured CloudEvents. +### CloudEvents mapper + +Maps incoming [CloudEvents](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md) to +Ditto Protocol. Supports both Binary and Structured CloudEvents. -**Note**: The mapper supports incoming Structured CloudEvents messages with `content-type:application/cloudevents+json` and Binary CloudEvents message with `content-type:application/vnd.eclipse.ditto+json` +**Note**: The mapper supports incoming Structured CloudEvents messages with `content-type:application/cloudevents+json` and Binary CloudEvents messages with `content-type:application/vnd.eclipse.ditto+json`. #### CloudEvents examples @@ -458,7 +405,6 @@ For example, a Binary CloudEvent for Ditto would look like this: A Structured CloudEvent for Ditto would look like this: ``` - headers: content-type:application/cloudevents+json ``` @@ -493,21 +439,19 @@ headers: } ``` -## Example connection with multiple mappers +## Using multiple mappers -The following example connection defines a `ConnectionStatus` mapping with the ID `status` and references it in a source. -Messages received via this source will be mapped by the `Ditto` mapping and the `ConnectionStatus` mapping. -The `Ditto` mapping requires no options to be configured, so you can directly use its alias `Ditto`. +Reference multiple mappers in a source's `payloadMapping` array. Define custom mappers in +`mappingDefinitions`: ```json -{ +{ "name": "exampleConnection", "sources": [{ - "addresses": [""], - "authorizationContext": ["ditto:inbound"], - "payloadMapping": ["Ditto", "status"] - } - ], + "addresses": [""], + "authorizationContext": ["ditto:inbound"], + "payloadMapping": ["Ditto", "status"] + }], "mappingDefinitions": { "status": { "mappingEngine": "ConnectionStatus", @@ -519,30 +463,15 @@ The `Ditto` mapping requires no options to be configured, so you can directly us } ``` -{% include note.html content="Starting aliases with an uppercase character and IDs with a lowercase character is - encouraged to avoid confusion but this is not enforced. "%} - - +{% include note.html content="Start aliases with an uppercase character and IDs with a lowercase character to distinguish them clearly. This convention is not enforced. "%} -## Example connection with mapping conditions +## Mapping conditions -The following example connection defines `incomingConditions` and `outgoingConditions`for the ConnectionStatus -mapping engine. -Optional incomingConditions are validated before the mapping of inbound messages. -Optional outgoingConditions are validated before the mapping of outbound messages. -Conditional Mapping can be achieved by using [function expressions](basic-placeholders.html#function-expressions). -When multiple incoming or outgoing conditions are set for one `mappingEngine`, -all have to equal true for the mapping to be executed. +You can add `incomingConditions` and `outgoingConditions` to control when a mapper executes. +All conditions must evaluate to true for the mapping to run: ```json -{ - "name": "exampleConnection", - "sources": [{ - "addresses": [""], - "authorizationContext": ["ditto:inbound"], - "payloadMapping": ["status"] - } - ], +{ "mappingDefinitions": { "status": { "mappingEngine": "ConnectionStatus", @@ -560,103 +489,52 @@ all have to equal true for the mapping to be executed. } ``` - - ## JavaScript mapping engine -Ditto utilizes the [Rhino](https://github.com/mozilla/rhino) JavaScript engine for Java for evaluating the JavaScript -to apply for mapping payloads. - -Using Rhino instead of Nashorn, the newer JavaScript engine shipped with Java, has the benefit that sandboxing can be -applied in a better way. - -Sandboxing of different payload scripts is required as Ditto is intended to be run as cloud service where multiple -connections to different endpoints are managed for different tenants at the same time. This requires the isolation of -each single script to avoid interference with other scripts and to protect the JVM executing the script against harmful -code execution. - - -### Constraints +Ditto uses the [Rhino](https://github.com/mozilla/rhino) JavaScript engine (version `1.7.14`, +ES6 flag enabled) with strict sandboxing for security. -Rhino does not fully support EcmaScript 6. Check which language constructs are supported before using -them in a mapping function. See [https://mozilla.github.io/rhino/compat/engines.html](https://mozilla.github.io/rhino/compat/engines.html). +### Sandboxing constraints -Ditto currently includes Rhino version `1.7.14` and has the `VERSION_ES6` flag enabled. +* No access to Java packages or classes +* No file access, network calls, or `exit`/`quit`/`print` +* Endless loops and deep recursion are terminated +* Script file size is limited +* No foreign JS library loading (unless included inline) -#### Sandboxing - -For sandboxing/security reasons following restrictions apply: - - -* access to Java packages and classes is not possible -* using `exit`, `quit`, `print`, etc. is not possible -* file access is not possible -* doing remote calls (e.g. to foreign web-servers) is not possible -* programming an endless-loop will terminate the script -* programming a recursion will terminate the script -* the file size of the script is limited -* no foreign JS libraries can be loaded (unless they fit in the file size limit and are included into the mapping script) +Check [Rhino compatibility](https://mozilla.github.io/rhino/compat/engines.html) for supported +ES6 features. ### Helper libraries -In order to work more conveniently with binary payloads, the following libraries may be loaded for payload transformations: - +You can load these libraries via `specificConfig` options: -* [bytebuffer.js](https://github.com/dcodeIO/bytebuffer.js) a ByteBuffer implementation using ArrayBuffers -* [long.js](https://github.com/dcodeIO/long.js) for representing a 64-bit two's-complement integer value +* [bytebuffer.js](https://github.com/dcodeIO/bytebuffer.js) -- `ArrayBuffer` manipulation +* [long.js](https://github.com/dcodeIO/long.js) -- 64-bit integer support +### Adding CommonJS modules -### Adding additional JS libraries +Configure `CONNECTIVITY_MESSAGE_MAPPING_JS_COMMON_JS_MODULE_PATH` to point to a directory +containing CommonJS modules (for example, via a Docker volume mount): -The used [Rhino JS engine](https://github.com/mozilla/rhino) allows making use of "CommonJS" in order to load JS -modules via `require('')` into the engine. -This feature is exposed to Ditto, configuring the configuration key `commonJsModulePath` or environment variable -`CONNECTIVITY_MESSAGE_MAPPING_JS_COMMON_JS_MODULE_PATH` of the connectivity service to a path in the -connectivity Docker container where to load additional CommonJS modules from - e.g. use a volume mount in order to get -additional JS modules into the container. - -For example, configure this variable to a folder to which you add (our mount) JavaScript libraries: ``` CONNECTIVITY_MESSAGE_MAPPING_JS_COMMON_JS_MODULE_PATH=/opt/commonjs-modules/ ``` -Then, for example, put [`pbf.js`](https://www.npmjs.com/package/pbf) (or any other JS library you want to use) -into that folder. +Then use `require()` in your scripts: -Afterwards, the library can be used in your JS snippet using: ```javascript var Pbf = require('pbf'); ``` - ### Helper functions -Ditto comes with a few helper functions, which makes writing the mapping scripts easier. They are available under the -`Ditto` scope: +Ditto provides these functions under the `Ditto` scope: ```javascript -/** - * Builds a Ditto Protocol message from the passed parameters. - * @param {string} namespace - The namespace of the entity in java package notation, e.g.: "org.eclipse.ditto". Or "_" - * (underscore) for connection announcements. - * @param {string} name - The name of the entity, e.g.: "device". - * @param {string} channel - The channel for the signal: "twin"|"live"|"none" - * @param {string} group - The affected group/entity: "things"|"policies"|"connections". - * @param {string} criterion - The criterion to apply: "commands"|"events"|"search"|"messages"|"announcements"|"errors". - * @param {string} action - The action to perform: "create"|"retrieve"|"modify"|"delete". Or the announcement name: - * "opened"|"closed"|"subjectDeletion". Or the subject of the message. - * @param {string} path - The path which is affected by the message (e.g.: "/attributes"), or the destination - * of a message (e.g.: "inbox"|"outbox"). - * @param {Object.} dittoHeaders - The headers Object containing all Ditto Protocol header values. - * @param {*} [value] - The value to apply / which was applied (e.g. in a "modify" action). - * @param {number} [status] - The status code that indicates the result of the command. If setting a status code, - * the Ditto Protocol Message will be interpreted as a response (e.g. content will be ignored when using 204). - * @param {Object} [extra] - The enriched extra fields when selected via "extraFields" option. - * @returns {DittoProtocolMessage} dittoProtocolMessage(s) - - * The mapped Ditto Protocol message or - * null if the message could/should not be mapped - */ -function buildDittoProtocolMsg(namespace, name, group, channel, criterion, action, path, dittoHeaders, value, status, extra) { +// Builds a Ditto Protocol message from the passed parameters. +function buildDittoProtocolMsg(namespace, name, group, channel, + criterion, action, path, dittoHeaders, value, status, extra) { const topic = buildTopic(namespace, name, group, channel, criterion, action); return { @@ -669,34 +547,14 @@ function buildDittoProtocolMsg(namespace, name, group, channel, criterion, actio }; } -/** - * Builds a Ditto Protocol topic from the passed parameters. - * @param {string} namespace - The namespace of the entity in java package notation, e.g.: "org.eclipse.ditto". Or "_" - * (underscore) for connection announcements. - * @param {string} name - The name of the entity, e.g.: "device". - * @param {string} channel - The channel for the signal: "twin"|"live"|"none" - * @param {string} group - The affected group/entity: "things"|"policies"|"connections". - * @param {string} criterion - The criterion to apply: "commands"|"events"|"search"|"messages"|"announcements"|"errors". - * @param {string} action - The action to perform: "create"|"retrieve"|"modify"|"delete". Or the announcement name: - * "opened"|"closed"|"subjectDeletion". Or the subject of the message. - * @returns {string} topic - the topic. - */ +// Builds a Ditto Protocol topic string. function buildTopic(namespace, name, group, channel, criterion, action) { const topicChannel = 'none' === channel ? '' : '/' + channel; return namespace + "/" + name + "/" + group + topicChannel + "/" + criterion + "/" + action; } -/** - * Builds an external message from the passed parameters. - * @param {Object.} headers - The external headers Object containing header values - * @param {string} [textPayload] - The external mapped String - * @param {ArrayBuffer} [bytePayload] - The external mapped bytes as ArrayBuffer - * @param {string} [contentType] - The returned Content-Type - * @returns {ExternalMessage} externalMessage - - * the mapped external message - * or null if the message could/should not be mapped - */ +// Builds an external message from the passed parameters. function buildExternalMsg(headers, textPayload, bytePayload, contentType) { return { @@ -707,24 +565,13 @@ function buildExternalMsg(headers, textPayload, bytePayload, contentType) { }; } -/** - * Transforms the passed ArrayBuffer to a String interpreting the content of the passed arrayBuffer as unsigned 8 - * bit integers. - * - * @param {ArrayBuffer} arrayBuffer the ArrayBuffer to transform to a String - * @returns {String} the transformed String - */ +// Transforms an ArrayBuffer to a String (unsigned 8-bit integers). function arrayBufferToString(arrayBuffer) { return String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)); } -/** - * Transforms the passed String to an ArrayBuffer using unsigned 8 bit integers. - * - * @param {String} string the String to transform to an ArrayBuffer - * @returns {ArrayBuffer} the transformed ArrayBuffer - */ +// Transforms a String to an ArrayBuffer (unsigned 8-bit integers). function stringToArrayBuffer(string) { let buf = new ArrayBuffer(string.length); @@ -735,14 +582,9 @@ function stringToArrayBuffer(string) { return buf; } -/** - * Transforms the passed ArrayBuffer to a {ByteBuffer} (from bytebuffer.js library which needs to be loaded). - * - * @param {ArrayBuffer} arrayBuffer the ArrayBuffer to transform - * @returns {ByteBuffer} the transformed ByteBuffer - */ +// Transforms an ArrayBuffer to a ByteBuffer (requires bytebuffer.js). function asByteBuffer(arrayBuffer) { - + let byteBuffer = new ArrayBuffer(arrayBuffer.byteLength); new Uint8Array(byteBuffer).set(new Uint8Array(arrayBuffer)); return dcodeIO.ByteBuffer.wrap(byteBuffer); @@ -751,20 +593,9 @@ function asByteBuffer(arrayBuffer) { ### Mapping incoming messages -Incoming external messages can be mapped to Ditto Protocol conform messages by implementing the following JavaScript function: +Implement `mapToDittoProtocolMsg` to convert external payloads to Ditto Protocol: ```javascript -/** - * Maps the passed parameters to a Ditto Protocol message. - * @param {Object.} headers - The headers Object containing all received header values - * @param {string} [textPayload] - The String to be mapped - * @param {ArrayBuffer} [bytePayload] - The bytes to be mapped as ArrayBuffer - * @param {string} [contentType] - The received Content-Type, e.g. "application/json" - * @returns {(DittoProtocolMessage|Array)} dittoProtocolMessage(s) - - * the mapped Ditto Protocol message, - * an array of Ditto Protocol messages or - * null if the message could/should not be mapped - */ function mapToDittoProtocolMsg( headers, textPayload, @@ -772,25 +603,23 @@ function mapToDittoProtocolMsg( contentType ) { - // ### Insert/adapt your mapping logic here. - // Use helper function Ditto.buildDittoProtocolMsg to build Ditto protocol message - // based on incoming payload. - // See https://websites.eclipseprojects.io/ditto/connectivity-mapping.html#helper-functions for details. - // ### example code assuming the Ditto protocol content type for incoming messages. + // Insert/adapt your mapping logic here. + // Use Ditto.buildDittoProtocolMsg to build Ditto Protocol messages from incoming payload. if (contentType === 'application/vnd.eclipse.ditto+json') { - // Message is sent as Ditto protocol text payload and can be used directly + // Message is already in Ditto Protocol format -- use directly return JSON.parse(textPayload); } else if (contentType === 'application/octet-stream') { - // Message is sent as binary payload; assume Ditto protocol message (JSON). + // Binary payload -- assume Ditto Protocol message (JSON) try { return JSON.parse(Ditto.arrayBufferToString(bytePayload)); } catch (e) { - // parsing failed (no JSON document); return null to drop the message + // parsing failed (no JSON document); drop the message return null; } } else if (contentType === 'application/json') { let parsedJson = JSON.parse(textPayload); - // the following variables would be determined from the "parsedJson" and from the "headers": + value = parsedJson.number1 + parsedJson['sub-field']; // access JSON keys with dashes using bracket notation + // determine these variables from parsedJson and headers: let namespace = ""; let name = ""; let group = "things"; @@ -803,128 +632,56 @@ function mapToDittoProtocolMsg( "a": 1 }; return Ditto.buildDittoProtocolMsg( - namespace, - name, - group, - channel, - criterion, - action, - path, - dittoHeaders, + namespace, + name, + group, + channel, + criterion, + action, + path, + dittoHeaders, value) } - // no mapping logic matched; return null to drop the message + // no mapping logic matched; drop the message return null; } ``` -The result of the function has to be a JavaScript object in [Ditto Protocol](protocol-overview.html) or an array of -such JavaScript objects. That's where the helper method `Ditto.buildDittoProtocolMsg` is useful: -it explicitly defines which parameters are required for the Ditto Protocol message. +Return a single Ditto Protocol message, an array of messages, or `null` to drop the message. -There is another JavaScript function which is helpful when access to the complete external message is needed. -It is possible to define the `mapToDittoProtocolMsgWrapper` in the incoming payload mapping and access the original -`externalMsg`. +For full access to the external message object, implement `mapToDittoProtocolMsgWrapper` instead: -This is the default implementation of `mapToDittoProtocolMsgWrapper`, delegating to `mapToDittoProtocolMsg`: ```javascript -/** - * Maps the passed external message to a Ditto Protocol message. - * @param {ExternalMessage} externalMsg - The external message to map to a Ditto Protocol message - * @returns {(DittoProtocolMessage|Array)} dittoProtocolMessage(s) - - * The mapped Ditto Protocol message, - * an array of Ditto Protocol messages or - * null if the message could/should not be mapped - */ function mapToDittoProtocolMsgWrapper(externalMsg) { - let headers = externalMsg.headers; let textPayload = externalMsg.textPayload; let bytePayload = externalMsg.bytePayload; let contentType = externalMsg.contentType; - return mapToDittoProtocolMsg(headers, textPayload, bytePayload, contentType); } ``` ### Mapping outgoing messages -Outgoing Ditto Protocol messages (e.g. [responses](basic-signals-commandresponse.html) or [events](basic-signals-event.html)) -can be mapped to external messages by implementing the following JavaScript function: +Implement `mapFromDittoProtocolMsg` to convert Ditto Protocol messages to external format: ```javascript -/** - * Maps the passed parameters which originated from a Ditto Protocol message to an external message. - * @param {string} namespace - The namespace of the entity in java package notation, e.g.: "org.eclipse.ditto". Or "_" - * (underscore) for connection announcements. - * @param {string} name - The name of the entity, e.g.: "device". - * @param {string} group - The affected group/entity: "things"|"policies"|"connections". - * @param {string} channel - The channel for the signal: "twin"|"live"|"none" - * @param {string} criterion - The criterion to apply: "commands"|"events"|"search"|"messages"|"announcements"| - * "errors". - * @param {string} action - The action to perform: "create"|"retrieve"|"modify"|"delete". Or the announcement name: - * "opened"|"closed"|"subjectDeletion". Or the subject of the message. - * @param {string} path - The path which is affected by the message (e.g.: "/attributes"), or the destination - * of a message (e.g.: "inbox"|"outbox"). - * @param {Object.} dittoHeaders - The headers Object containing all Ditto Protocol header values. - * @param {*} [value] - The value to apply / which was applied (e.g. in a "modify" action). - * @param {number} [status] - The status code that indicates the result of the command. When this field is set, - * it indicates that the Ditto Protocol Message contains a response. - * @param {Object} [extra] - The enriched extra fields when selected via "extraFields" option. - * @returns {(ExternalMessage|Array)} externalMessage - The mapped external message, an array of - * external messages or null if the message could/should not be mapped. - */ -function mapFromDittoProtocolMsg( - namespace, - name, - group, - channel, - criterion, - action, - path, - dittoHeaders, - value, - status, - extra -) { - - // ### - // Insert your mapping logic here - // ### example code using the Ditto protocol content type. +function mapFromDittoProtocolMsg(namespace, name, group, channel, + criterion, action, path, dittoHeaders, value, status, extra) { let headers = dittoHeaders; - let textPayload = JSON.stringify(Ditto.buildDittoProtocolMsg(namespace, name, group, channel, criterion, action, - path, dittoHeaders, value, status, extra)); - let bytePayload = null; - let contentType = 'application/vnd.eclipse.ditto+json'; - return Ditto.buildExternalMsg( - headers, // The external headers Object containing header values - textPayload, // The external mapped String - bytePayload, // The external mapped byte[] - contentType // The returned Content-Type + let textPayload = JSON.stringify( + Ditto.buildDittoProtocolMsg(namespace, name, group, channel, + criterion, action, path, dittoHeaders, value, status, extra) ); + return Ditto.buildExternalMsg(headers, textPayload, null, + 'application/vnd.eclipse.ditto+json'); } ``` -The result of the function has to be a JavaScript object or, an array of JavaScript objects with the fields `headers`, -`textPayload`, `bytePayload` and `contentType`. That's where the helper method `Ditto.buildExternalMsg` is useful: -it explicitly defines which parameters are required for the external message. - -There is another JavaScript function which is helpful when access to the complete Ditto protocol message is needed. -It is possible to define the `mapFromDittoProtocolMsgWrapper` in the outgoing payload mapping and access the -original `dittoProtocolMsg`. -Please refer to the [Ditto Protocol specification](protocol-specification.html#dittoProtocolEnvelope) -to inspect which JSON fields are available when. +For access to the full Ditto Protocol message (including `revision`), implement +`mapFromDittoProtocolMsgWrapper`: -This is the default implementation of `mapFromDittoProtocolMsgWrapper`, delegating to `mapFromDittoProtocolMsg`: ```javascript -/** - * Maps the passed Ditto Protocol message to an external message. - * @param {DittoProtocolMessage} dittoProtocolMsg - The Ditto Protocol message to map - * @returns {(ExternalMessage|Array)} externalMessage - - * The mapped external message, - * an array of external messages or - * null if the message could/should not be mapped - */ function mapFromDittoProtocolMsgWrapper(dittoProtocolMsg) { let topic = dittoProtocolMsg.topic; @@ -957,67 +714,26 @@ function mapFromDittoProtocolMsgWrapper(dittoProtocolMsg) { } ``` -## JavaScript payload types - -Both, text payloads and byte payloads may be mapped. - -### Text payloads - -Working with text payloads is as easy as it gets in JavaScript. For example, for the content-type `application/json` -structured data may be processed like this: - -```javascript -let value; -if (contentType === 'application/json') { - let parsedJson = JSON.parse(textPayload); - value = parsedJson.number1 + parsedJson['sub-field']; // remember to access JSON keys with dashes in a JS special way -} else { - // a script may decide to not map other content-types than application/json - return null; -} -// proceed ... -``` - -### Byte payloads - -Working with byte payloads is also possible but does require a little bit of knowledge about JavaScript's -[ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) -[TypedArrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) and -[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView). - -What you get in the mapping scripts is a `bytePayload` of type `ArrayBuffer` which lets you work on the bytes -in different ways: +### Working with byte payloads -#### Typed Arrays - -> A TypedArray \[is\] a view into an ArrayBuffer where every item has the same size and type.
    [source](https://hacks.mozilla.org/2017/01/typedarray-or-dataview-understanding-byte-order/) - -With TypedArrays you can simply wrap the `bytePayload` `ArrayBuffer` and work on all the items e.g. -as unsigned 8-bit integers: +Use [TypedArrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) +or [DataViews](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) +to process binary data: ```javascript +// TypedArray approach let bytes = new Uint8Array(bytePayload); -bytes[0]; // access the first byte -bytes[1]; // access the second byte -``` - -#### DataViews -> The DataView \[is\] another view into an ArrayBuffer, but one which allows items of different size and type in the ArrayBuffer.
    [source](https://hacks.mozilla.org/2017/01/typedarray-or-dataview-understanding-byte-order/) - -```javascript +// DataView approach (mixed types) let view = new DataView(bytePayload); -view.getInt8(0); // access a 8-bit signed integer (byte) on offset=0 -view.getUint16(1); // access a 16-bit unsigned integer (usigned short) on offset=1 -``` - -DataViews also allow to `set` bytes to an underlying ArrayBuffer conveniently. - -#### ByteBuffer.js +view.getInt8(0); // 8-bit signed integer (byte) at offset 0 +view.getUint16(1); // 16-bit unsigned integer (unsigned short) at offset 1 +let temp = view.getInt16(0) / 100.0; // 16-bit signed int at offset 0 +let pressure = view.getInt16(2); // 16-bit signed int at offset 2 +let humidity = view.getUint8(4); // 8-bit unsigned int at offset 4 +``` -Alternatively, Ditto's JavaScript transformation may be loaded with the [above mentioned](#helper-libraries) libraries, -e.g. "bytebuffer.js".
    -With `ByteBuffer`, the content of an `ArrayBuffer` can be accessed in a buffered way: +Or use `ByteBuffer.js` (load with `"loadBytebufferJS": "true"`): ```javascript let byteBuf = Ditto.asByteBuffer(bytePayload); @@ -1031,225 +747,88 @@ buf.readUTF8String(4); // read 4 characters of UTF-8 encoded string + advances t buf.remaining(); // gets the number of remaining readable bytes in the buffer ``` -Check the [ByteBuffer API documentation](https://github.com/dcodeIO/bytebuffer.js/wiki/API) to find out what is possible -with that helper. +Check the [ByteBuffer API documentation](https://github.com/dcodeIO/bytebuffer.js/wiki/API) for the full list of operations. - -## JavaScript Examples +## JavaScript examples ### Text payload example -Let's assume your device sends telemetry data via [Eclipse Hono's](https://www.eclipse.org/hono/) MQTT adapter -into the cloud. And, that an example payload of your device is: +Device sends JSON telemetry: ```json -{ - "temp": "23.42 °C", - "hum": 78, - "pres": { - "value": 760, - "unit": "mmHg" - } -} -``` - -We want to map a single message of this device containing updates for all 3 values to a Thing in the following structure: - -```json -{ - "thingId": "the.namespace:the-thing-id", - "policyId": "the.namespace:the-policy-id", - "features": { - "temperature": { - "properties": { - "value": 23.42 - } - }, - "pressure": { - "properties": { - "value": 760 - } - }, - "humidity": { - "properties": { - "value": 78 - } - } - } -} +{"temp": "23.42 °C", "hum": 78, "pres": {"value": 760, "unit": "mmHg"}} ``` -Therefore, we define following `incoming` mapping function: +Mapping to update thing features: ```javascript -function mapToDittoProtocolMsg( - headers, - textPayload, - bytePayload, - contentType -) { - - if (contentType !== 'application/json') { - return null; // only handle messages with content-type application/json - } - - let jsonData = JSON.parse(textPayload); - - let value = { - temperature: { - properties: { - value: jsonData.temp.split(" ")[0] // omit the unit - } - }, - pressure: { - properties: { - value: jsonData.pres.value - } - }, - humidity: { - properties: { - value: jsonData.hum - } - } - }; +function mapToDittoProtocolMsg(headers, textPayload, bytePayload, contentType) { + if (contentType !== 'application/json') return null; + + let jsonData = JSON.parse(textPayload); + let value = { + temperature: { properties: { value: parseFloat(jsonData.temp.split(" ")[0]) } }, + pressure: { properties: { value: jsonData.pres.value } }, + humidity: { properties: { value: jsonData.hum } } + }; - return Ditto.buildDittoProtocolMsg( - 'org.eclipse.ditto', // in this example always the same - headers['device_id'], // Eclipse Hono sets the authenticated device_id as AMQP 1.0 header - 'things', // we deal with a Thing - 'twin', // we want to update the twin - 'commands', // we want to create a command to update a twin - 'modify', // modify the twin - '/features', // modify all features at once - headers, // pass through the headers from AMQP 1.0 - value - ); + return Ditto.buildDittoProtocolMsg( + 'org.eclipse.ditto', headers['device_id'], + 'things', 'twin', 'commands', 'modify', + '/features', headers, value + ); } ``` -When your device now sends its payload via the MQTT adapter of Eclipse Hono: +Send this payload via Eclipse Hono's MQTT adapter: ```bash mosquitto_pub -u 'sensor1@DEFAULT_TENANT' -P hono-secret -t telemetry -m '{"temp": "23.42 °C","hum": 78,"pres": {"value": 760,"unit": "mmHg"}}' ``` -Your digital twin is updated by applying the specified script and extracting the relevant values from the passed `textPayload`. +The digital twin is updated by applying the script and extracting the relevant values from the `textPayload`. +### Binary payload example -### Bytes payload example - -For this example, let's assume your device sends telemetry data via [Eclipse Hono's](https://www.eclipse.org/hono/) -HTTP adapter into the cloud. An example payload of your device - displayed as hexadecimal - is: - -``` -0x09EF03F72A -``` - -Let us now also assume that - -* the first 2 bytes `09 EF` represent - * the temperature as 16bit signed integer (thus, may also be negative) - * this is not a float in oder to save space (as float needs at least 32 bit) -* the second 2 bytes `03 F7` represent the pressure as 16bit signed integer -* the last byte `2A` represents the humidity as 8bit unsigned integer of our device. - -We want to map a single message of this device containing updates for all 3 values to a Thing in the following structure: - -```json -{ - "thingId": "the.namespace:the-thing-id", - "policyId": "the.namespace:the-policy-id", - "features": { - "temperature": { - "properties": { - "value": 25.43 - } - }, - "pressure": { - "properties": { - "value": 1015 - } - }, - "humidity": { - "properties": { - "value": 42 - } - } - } -} -``` +Device sends 5 bytes as hexadecimal `0x09EF03F72A`: -Therefore, we define following `incoming` mapping function: +* the first 2 bytes `09 EF` represent the temperature as 16-bit signed integer (not a float, to save space) +* the next 2 bytes `03 F7` represent the pressure as 16-bit signed integer +* the last byte `2A` represents the humidity as 8-bit unsigned integer ```javascript -function mapToDittoProtocolMsg( - headers, - textPayload, - bytePayload, - contentType -) { - - if (contentType !== 'application/octet-stream') { - return null; // only handle messages with content-type application/octet-stream - } - - let view = new DataView(bytePayload); - - let value = { - temperature: { - properties: { - // interpret the first 2 bytes (16 bit) as signed int and divide through 100.0: - value: view.getInt16(0) / 100.0 - } - }, - pressure: { - properties: { - // interpret the next 2 bytes (16 bit) as signed int: - value: view.getInt16(2) - } - }, - humidity: { - properties: { - // interpret the next 1 bytes (8 bit) as unsigned int: - value: view.getUint8(4) - } - } - }; +function mapToDittoProtocolMsg(headers, textPayload, bytePayload, contentType) { + if (contentType !== 'application/octet-stream') return null; + + let view = new DataView(bytePayload); + let value = { + temperature: { properties: { value: view.getInt16(0) / 100.0 } }, + pressure: { properties: { value: view.getInt16(2) } }, + humidity: { properties: { value: view.getUint8(4) } } + }; - return Ditto.buildDittoProtocolMsg( - 'org.eclipse.ditto', // in this example always the same - headers['device_id'], // Eclipse Hono sets the authenticated device_id as AMQP 1.0 header - 'things', // we deal with a Thing - 'twin', // we want to update the twin - 'commands', // we want to create a command to update a twin - 'modify', // modify the twin - '/features', // modify all features at once - headers, // pass through the headers from AMQP 1.0 - value - ); + return Ditto.buildDittoProtocolMsg( + 'org.eclipse.ditto', headers['device_id'], + 'things', 'twin', 'commands', 'modify', + '/features', headers, value + ); } ``` -When your device now sends its payload via the HTTP adapter of Eclipse Hono: +Send this payload via Eclipse Hono's HTTP adapter: ```bash echo -e $((0x09EF03F72A)) | curl -i -X POST -u sensor1@DEFAULT_TENANT:hono-secret -H 'Content-Type: application/octet-stream' --data-binary @- http://127.0.0.1:8080/telemetry ``` -Your digital twin is updated by applying the specified script and extracting the relevant values from the passed `bytePayload`. - +The digital twin is updated by applying the script and extracting the relevant values from the `bytePayload`. -## Custom Java based implementation +## Custom Java mapper -Beside the JavaScript based mapping - which can be configured/changed at runtime without the need of restarting the -connectivity service - there is also the possibility to implement a custom Java based mapper. +For advanced use cases, implement a custom Java-based mapper by extending +[`AbstractMessageMapper`](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/mapping/AbstractMessageMapper.java). -The interface to be implemented is -[`MessageMapper`](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/mapping/MessageMapper.java)) -and there is an abstract class [`AbstractMessageMapper`](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/mapping/AbstractMessageMapper.java) -which eases implementation of a custom mapper. - -Simply extend from `AbstractMessageMapper` to provide a custom mapper: +Extend `AbstractMessageMapper` to provide a custom mapper: ```java public final class FooMapper extends AbstractMessageMapper { @@ -1304,41 +883,28 @@ public final class FooMapper extends AbstractMessageMapper { } ``` -After instantiation of the custom `MessageMapper`, the `doConfigure` method is called with all the *options* which were -provided to the mapper in the [configured connection](connectivity-manage-connections.html#create-connection). -Use them in order to pass in configurations, thresholds, etc. - -Then, simply implement both of the `map` methods: - -* `List map(ExternalMessage message)` maps from an incoming external message to - * an empty list of `Adaptable`s if the incoming message should be dropped - * a list of one or many [Ditto Protocol](protocol-overview.html) `Adaptable`s -* `List map(Adaptable adaptable)` maps from an outgoing [Ditto Protocol](protocol-overview.html) `Adaptable` to - * an empty list of `ExternalMessage`s if the outgoing message should be dropped - * a list of one or many external messages - -In order to use this custom Java based mapper implementation, the following steps are required: - -* the alias has to be defined via the implemented `getAlias()` method - it must be unique and *should* start with an uppercase letter -* if the custom mapper requires mandatory options then implement `isConfigurationMandatory()` to return `true` -* the mapper class needs to be on the classpath of the [connectivity](architecture-services-connectivity.html) - microservice in order to be loaded. - Follow the instructions of - [how to extend Ditto](installation-extending.html#providing-additional-functionality-by-adding-jars-to-the-classpath) - to achieve that. -* the mapper needs to be registered via configuration in the connectivity service, - [extend the configuration](installation-extending.html#adjusting-configuration-of-ditto) or add the mapper via - [system properties](installation-operating.html#ditto-configuration) configuration -* when creating a new connection you have to specify the alias of your mapper as the `mappingEngine` in the - connection's `mappingDefinitions` and reference the ID of your mapper in a source or a target +Key methods to implement: + +* `List map(ExternalMessage message)` -- inbound mapping (return empty list to drop) +* `List map(Adaptable adaptable)` -- outbound mapping (return empty list to drop) +* `String getAlias()` -- unique mapper alias (must start with uppercase) + +To deploy: + +1. Add the mapper JAR to the connectivity service classpath + ([extending Ditto](installation-extending.html#adding-jars-to-the-classpath)) +2. Register the alias in [connectivity configuration](installation-extending.html#adjusting-service-configuration) +3. Reference the alias in your connection's `mappingDefinitions` {% include tip.html content="If your mapper does not require any options (`isConfigurationMandatory() = true`), you can - directly reference the alias in a source or a target without first defining it inside `mappingDefinitions`." %} + directly reference the alias in a source or a target without first defining it inside `mappingDefinitions`." %} -### Example for Custom Java based mapper +For a complete example, see the +[custom-ditto-java-payload-mapper](https://github.com/eclipse-ditto/ditto-examples/tree/master/custom-ditto-java-payload-mapper) +project. -Please have a look at the following Ditto example project: -* [custom-ditto-java-payload-mapper](https://github.com/eclipse-ditto/ditto-examples/tree/master/custom-ditto-java-payload-mapper) +## Further reading -This shows how to implement, add and configure a custom, Protobuf based, Java payload mapper for Ditto to use in the -connectivity service for mapping a custom domain specific Protbuf encoded payload. +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Header mapping](connectivity-header-mapping.html) -- map external headers +* [Ditto Protocol](protocol-overview.html) -- message format specification diff --git a/documentation/src/main/resources/pages/ditto/connectivity-overview.md b/documentation/src/main/resources/pages/ditto/connectivity-overview.md index b74f219c4d1..3d8da61217e 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-overview.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-overview.md @@ -1,28 +1,18 @@ --- -title: Connectivity API overview -keywords: +title: Connectivity overview +keywords: connectivity, connections, overview, AMQP, MQTT, HTTP, Kafka, Hono tags: [connectivity] permalink: connectivity-overview.html --- -The Connectivity API is a bare management API for Ditto's [Connectivity Service](architecture-services-connectivity.html). -It is available: -* via [DevOps Commands](installation-operating.html#devops-commands) -* via HTTP API with a specific authentication - for details see [Manage connections](connectivity-manage-connections.html) +Ditto's Connectivity service lets you integrate with external messaging systems and backends using protocols like AMQP, MQTT, HTTP, and Kafka. -Use it to manage client [connections](basic-connections.html) to remote systems and to exchange -[Ditto Protocol](protocol-specification.html) messages with those. -If a remote system is unable to send messages in the necessary format, there is the option -to configure custom [payload mapping logic](connectivity-mapping.html) to adapt to almost any message format and -encoding. +For the connection data model and core concepts, see [Connections](basic-connections.html). -The following connection types are supported: - - -* [AMQP 0.9.1](connectivity-protocol-bindings-amqp091.html) -* [AMQP 1.0](connectivity-protocol-bindings-amqp10.html) -* [MQTT 3.1.1](connectivity-protocol-bindings-mqtt.html) -* [MQTT 5](connectivity-protocol-bindings-mqtt5.html) -* [HTTP 1.1](connectivity-protocol-bindings-http.html) -* [Kafka 2.x](connectivity-protocol-bindings-kafka2.html) +This section covers the operational aspects of connectivity: +* **[Manage Connections](connectivity-manage-connections.html)** — Create, modify, retrieve, and delete connections via the HTTP API +* **[Piggyback Commands](connectivity-manage-connections-piggyback.html)** — Manage connections via DevOps commands +* **Protocol Bindings** — Protocol-specific configuration for [AMQP 0.9.1](connectivity-protocol-bindings-amqp091.html), [AMQP 1.0](connectivity-protocol-bindings-amqp10.html), [MQTT 3.1.1](connectivity-protocol-bindings-mqtt.html), [MQTT 5](connectivity-protocol-bindings-mqtt5.html), [HTTP 1.1](connectivity-protocol-bindings-http.html), [Kafka 2.x](connectivity-protocol-bindings-kafka2.html), and [Eclipse Hono](connectivity-protocol-bindings-hono.html) +* **Data Transformation** — [Payload mapping](connectivity-mapping.html) and [header mapping](connectivity-header-mapping.html) +* **Security** — [TLS certificates](connectivity-tls-certificates.html), [SSH tunneling](connectivity-ssh-tunneling.html), and [HMAC signing](connectivity-hmac-signing.html) diff --git a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-amqp091.md b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-amqp091.md index 8d6c996cc98..c6d5cdcee45 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-amqp091.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-amqp091.md @@ -5,84 +5,67 @@ tags: [protocol, connectivity, rql] permalink: connectivity-protocol-bindings-amqp091.html --- -Consume messages from AMQP 0.9.1 brokers via [sources](#source-format) and send messages to AMQP 0.9.1 brokers via -[targets](#target-format). +You use the AMQP 0.9.1 binding to connect Ditto with message brokers like RabbitMQ for consuming and publishing messages. -## Content-type +{% include callout.html content="**TL;DR**: Configure an AMQP 0.9.1 connection with `connectionType: \"amqp-091\"`. Source addresses are queue names, and target addresses use the format `exchange_name/routing_key`." type="primary" %} -When messages are sent in [Ditto Protocol](protocol-overview.html) (as `UTF-8` encoded String payload), -the `content-type` of AMQP 0.9.1 messages must be set to: +## Overview + +The AMQP 0.9.1 protocol binding lets you consume messages from AMQP 0.9.1 brokers via +[sources](#source-configuration) and publish messages via [targets](#target-configuration). + +When you send messages in [Ditto Protocol](protocol-overview.html) format (`UTF-8` encoded strings), +set the `content-type` to: ``` application/vnd.eclipse.ditto+json ``` -If messages, which are not in Ditto Protocol, should be processed, a [payload mapping](connectivity-mapping.html) must -be configured for the AMQP 0.9.1 connection in order to transform the messages. +For other payload formats, configure a [payload mapping](connectivity-mapping.html). -## AMQP 0.9.1 properties +### AMQP 0.9.1 properties -Supported AMQP 0.9.1 properties which are interpreted in a specific way are: +Ditto interprets these AMQP 0.9.1 properties: -* `content-type`: for defining the Ditto Protocol content-type -* `correlation-id`: for correlating request messages to responses +* `content-type` -- defines the Ditto Protocol content type +* `correlation-id` -- correlates request messages to responses -## Specific connection configuration +## Connection URI format -The common configuration for connections in [Connections > Sources](basic-connections.html#sources) and -[Connections > Targets](basic-connections.html#targets) applies here as well. -Following are some specifics for AMQP 0.9.1 connections: +``` +amqp://user:password@hostname:5672/vhost +``` -### Source format +Use `amqps://` for TLS-secured connections. -An AMQP 0.9.1 connection requires the protocol configuration source object to have an `addresses` property with a list -of queue names, and `authorizationContext` array that contains the authorization subjects, in whose context -incoming messages are processed. These subjects may contain placeholders, see -[placeholders](basic-connections.html#placeholder-for-source-authorization-subjects) section for more information. +## Source configuration +The common [source configuration](basic-connections.html#sources) applies. Source `addresses` are +AMQP 0.9.1 queue names: ```json { - "addresses": [ - "", - "..." - ], - "authorizationContext": ["ditto:inbound-auth-subject", "..."] + "addresses": ["queueName"], + "authorizationContext": ["ditto:inbound-auth-subject"] } ``` -#### Source acknowledgement handling - -For AMQP 0.9.1 sources, when configuring -[acknowledgement requests](basic-connections.html#source-acknowledgement-requests), consumed messages from the AMQP 0.9.1 -broker are treated in the following way: - -For Ditto acknowledgements with successful [status](protocol-specification-acks.html#combined-status-code): -* Acknowledges a single AMQP 0.9.1 message with an `Ack` message for the received `deliveryTag` +### Source acknowledgement handling -For Ditto acknowledgements with mixed successful/failed [status](protocol-specification-acks.html#combined-status-code): -* If some of the aggregated [acknowledgements](basic-acknowledgements.html#acknowledgements-acks) require redelivery (e.g. based on a timeout): - * Negatively acknowledges the AMQP 0.9.1 message with a `Nack` message for the received `deliveryTag` and setting `requeue` to `true` -* If none of the aggregated [acknowledgements](basic-acknowledgements.html#acknowledgements-acks) require redelivery: - * Negatively acknowledges the AMQP 0.9.1 message with a `Nack` message for the received `deliveryTag` and setting `requeue` to `false` preventing redelivery by the AMQP 0.9.1 broker +When you configure [acknowledgement requests](basic-connections.html#source-acknowledgement-requests): +* **Successful acknowledgements** -- Ditto sends an `Ack` for the received `deliveryTag` +* **Failed with redelivery needed** -- Ditto sends a `Nack` with `requeue: true` +* **Failed without redelivery** -- Ditto sends a `Nack` with `requeue: false` -### Target format - -An AMQP 0.9.1 connection requires the protocol configuration target object to have an `address` property with a combined -value of the `exchange_name` and `routing_key`. The target address may contain placeholders; see -[placeholders](basic-connections.html#placeholder-for-target-addresses) section for more information. - -Further, `"topics"` is a list of strings, each list entry representing a subscription of -[Ditto protocol topics](protocol-specification-topic.html). - -Outbound messages are published to the configured target address if one of the subjects in `"authorizationContext"` -has READ permission on the thing, which is associated with a message. +## Target configuration +The common [target configuration](basic-connections.html#targets) applies. The target `address` +combines the exchange name and routing key: ```json { - "address": "/", + "address": "exchangeName/routingKey", "topics": [ "_/_/things/twin/events", "_/_/things/live/messages" @@ -91,62 +74,49 @@ has READ permission on the thing, which is associated with a message. } ``` -#### Target acknowledgement handling - -For AMQP 0.9.1 targets, when configuring -[automatically issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label), requested -acknowledgements are produced in the following way: +The target address supports [placeholders](basic-connections.html#placeholder-for-target-addresses). -Once the AMQP 0.9.1 client signals that the message was acknowledged by the AMQP 0.9.1 broker, the following information -is mapped to the automatically created [acknowledgement](protocol-specification-acks.html#acknowledgement): -* Acknowledgement.status: - * will be `200`, if the message was successfully ACKed by the AMQP 0.9.1 broker - * will be `400`, if the AMQP 0.9.1 broker does not support publisher confirms - * will be `503`, if the AMQP 0.9.1 broker negatively confirmed receiving a message -* Acknowledgement.value: - * will be missing, for status `200` - * will contain more information, in case that an error `status` was set +### Target acknowledgement handling -### Specific configuration properties +When you configure [issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label): -There are no specific configuration properties available for this type of connection. +| Status | Condition | +|--------|-----------| +| `200` | Message successfully ACKed by the broker | +| `400` | Broker does not support publisher confirms | +| `503` | Broker negatively confirmed the message | -## Establishing connecting to an AMQP 0.9.1 endpoint +## Specific configuration options -Ditto's [Connectivity service](architecture-services-connectivity.html) is responsible for creating new and managing -existing connections. +There are no AMQP 0.9.1-specific configuration properties. -This can be done dynamically at runtime without the need to restart any microservice using a -[Ditto DevOps command](installation-operating.html#devops-commands). - -Example connection configuration to create a new AMQP 0.9.1 connection (e.g. in order to connect to a RabbitMQ): +## Example connection JSON ```json { - "connection": { - "id": "rabbit-example-connection-123", - "connectionType": "amqp-091", - "connectionStatus": "open", - "failoverEnabled": true, - "uri": "amqp://user:password@localhost:5672/vhost", - "sources": [ - { - "addresses": [ - "queueName" - ], - "authorizationContext": ["ditto:inbound-auth-subject", "..."] - } + "id": "rabbit-example-connection-123", + "connectionType": "amqp-091", + "connectionStatus": "open", + "failoverEnabled": true, + "uri": "amqp://user:password@localhost:5672/vhost", + "sources": [{ + "addresses": ["queueName"], + "authorizationContext": ["ditto:inbound-auth-subject"] + }], + "targets": [{ + "address": "exchangeName/routingKey", + "topics": [ + "_/_/things/twin/events", + "_/_/things/live/messages" ], - "targets": [ - { - "address": "exchangeName/routingKey", - "topics": [ - "_/_/things/twin/events", - "_/_/things/live/messages" - ], - "authorizationContext": ["ditto:outbound-auth-subject", "..."] - } - ] - } + "authorizationContext": ["ditto:outbound-auth-subject"] + }] } ``` + +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Payload mapping](connectivity-mapping.html) -- transform message payloads +* [Header mapping](connectivity-header-mapping.html) -- map external headers +* [TLS certificates](connectivity-tls-certificates.html) -- secure connections with TLS diff --git a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-amqp10.md b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-amqp10.md index 89a2857bb70..c0cd9813431 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-amqp10.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-amqp10.md @@ -5,44 +5,36 @@ tags: [protocol, connectivity, rql] permalink: connectivity-protocol-bindings-amqp10.html --- -Consume messages from AMQP 1.0 endpoints via [sources](#source-format) and send messages to AMQP 1.0 endpoints via -[targets](#target-format). +You use the AMQP 1.0 binding to connect Ditto with AMQP 1.0 endpoints such as Eclipse Hono or Azure Service Bus. -## Content-type +{% include callout.html content="**TL;DR**: Configure an AMQP 1.0 connection with `connectionType: \"amqp-10\"`. Source addresses are AMQP link names, and target addresses can be queues (`queue://`) or topics (`topic://`)." type="primary" %} -When messages are sent in [Ditto Protocol](protocol-overview.html) (as `UTF-8` encoded String payload), -the `content-type` of AMQP 1.0 messages must be set to: +## Overview + +The AMQP 1.0 protocol binding lets you consume messages from AMQP 1.0 endpoints via +[sources](#source-configuration) and publish messages via [targets](#target-configuration). + +When you send messages in [Ditto Protocol](protocol-overview.html) format (`UTF-8` encoded strings), +set the `content-type` to: ``` application/vnd.eclipse.ditto+json ``` -If messages, which are not in Ditto Protocol, should be processed, a [payload mapping](connectivity-mapping.html) must -be configured for the AMQP 1.0 connection in order to transform the messages. - -## AMQP 1.0 properties, application properties and message annotations - -When set as external headers by outgoing payload or header mapping, the properties defined by AMQP 1.0 specification are -set to the corresponding header value. Conversely, the values of AMQP 1.0 properties are available for incoming payload -and header mapping as external headers. The supported AMQP 1.0 properties are: - -* `message-id` -* `user-id` -* `to` -* `subject` -* `reply-to` -* `correlation-id` -* `content-type` -* `absolute-expiry-time` -* `creation-time` -* `group-id` -* `group-sequence` -* `reply-to-group-id` - -External headers not on this list are mapped to AMQP application properties. -To set an application property whose name is identical to an AMQP 1.0 property, prefix it by -`amqp.application.property:`. The following [target header mapping](basic-connections.html#target-header-mapping) sets -the application property `to` to the value of the Ditto protocol header `reply-to`: +For other payload formats, configure a [payload mapping](connectivity-mapping.html). + +### AMQP 1.0 properties and headers + +Ditto maps these standard AMQP 1.0 properties: + +`message-id`, `user-id`, `to`, `subject`, `reply-to`, `correlation-id`, `content-type`, +`absolute-expiry-time`, `creation-time`, `group-id`, `group-sequence`, `reply-to-group-id` + +Headers not in this list are mapped as AMQP application properties. To set an application property +whose name collides with a standard property, prefix it with `amqp.application.property:`. + +**Target** -- set the application property `to` to the Ditto protocol header `reply-to`: + ```json { "headerMapping": { @@ -51,9 +43,8 @@ the application property `to` to the value of the Ditto protocol header `reply-t } ``` -To read an application property whose name is identical to an AMQP 1.0 property, prefix it by -`amqp.application.property:`. The following [source header mapping](basic-connections.html#source-header-mapping) sets -the Ditto protocol header `reply-to` to the value of the application property `to`: +**Source** -- read the application property `to` into the Ditto protocol header `reply-to`: + ```json { "headerMapping": { @@ -62,9 +53,10 @@ the Ditto protocol header `reply-to` to the value of the application property `t } ``` -To set a message annotation, prefix it by `amqp.message.annotation:`. -The following [target header mapping](basic-connections.html#target-header-mapping) sets -the message annotation `to` to the value of the Ditto protocol header `reply-to`: +To set or read a message annotation, prefix with `amqp.message.annotation:`. + +**Target** -- set the message annotation `to`: + ```json { "headerMapping": { @@ -73,9 +65,8 @@ the message annotation `to` to the value of the Ditto protocol header `reply-to` } ``` -To read a message annotation, prefix it by `amqp.message.annotation:`. -The following [source header mapping](basic-connections.html#source-header-mapping) sets -the Ditto protocol header `reply-to` to the value of the message annotation `to`: +**Source** -- read the message annotation `to`: + ```json { "headerMapping": { @@ -86,116 +77,76 @@ the Ditto protocol header `reply-to` to the value of the message annotation `to` {% include note.html content="For now, setting or reading the AMQP 1.0 property 'content-encoding' is impossible." %} -## Specific connection configuration +## Connection URI format + +``` +amqps://user:password@hostname:5671 +``` -The common configuration for connections in [Connections > Sources](basic-connections.html#sources) and -[Connections > Targets](basic-connections.html#targets) applies here as well. -Following are some specifics for AMQP 1.0 connections: +Use `amqp://` for unencrypted connections (port 5672 typically). -### Source format +## Source configuration -Any `source` item defines an `addresses` array of source identifiers (e.g. Eclipse Hono's -Telemetry API) to consume messages from, -and `authorizationContext` array that contains the authorization subjects, in whose context -inbound messages are processed. These subjects may contain placeholders, see -[placeholders](basic-connections.html#placeholder-for-source-authorization-subjects) section for more information. +The common [source configuration](basic-connections.html#sources) applies. Source `addresses` are +AMQP 1.0 link names: ```json { - "addresses": [ - "", - "..." - ], - "authorizationContext": ["ditto:inbound-auth-subject", "..."] + "addresses": ["telemetry/FOO"], + "authorizationContext": ["ditto:inbound-auth-subject"] } ``` -#### Source acknowledgement handling - -For AMQP 1.0 sources, when configuring -[acknowledgement requests](basic-connections.html#source-acknowledgement-requests), consumed messages from the AMQP 1.0 -endpoint are treated in the following way: - -For Ditto acknowledgements with successful [status](protocol-specification-acks.html#combined-status-code): -* Acknowledges the AMQP 1.0 message with `accepted` outcome (see [AMQP 1.0 spec: 3.4.2 Accepted](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-accepted)) +### Source acknowledgement handling -For Ditto acknowledgements with mixed successful/failed [status](protocol-specification-acks.html#combined-status-code): -* If some of the aggregated [acknowledgements](basic-acknowledgements.html#acknowledgements-acks) require redelivery (e.g. based on a timeout): - * Negatively acknowledges the AMQP 1.0 message with `modified[delivery-failed]` outcome (see [AMQP 1.0 spec: 3.4.5 Modified](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-modified)) -* If none of the aggregated [acknowledgements](basic-acknowledgements.html#acknowledgements-acks) require redelivery: - * Negatively acknowledges the AMQP 1.0 message with `rejected` outcome (see [AMQP 1.0 spec: 3.4.3 Rejected](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-rejected)) preventing redelivery by the AMQP 1.0 endpoint +When you configure [acknowledgement requests](basic-connections.html#source-acknowledgement-requests): -### Target format +* **Successful** -- message acknowledged with `accepted` outcome +* **Failed with redelivery needed** -- message rejected with `modified[delivery-failed]` outcome +* **Failed without redelivery** -- message rejected with `rejected` outcome -An AMQP 1.0 connection requires the protocol configuration target object to have an `address` property with a source -identifier. The target address may contain placeholders; see -[placeholders](basic-connections.html#placeholder-for-target-addresses) section for more -information. +## Target configuration -Target addresses for AMQP 1.0 are by default handled as AMQP 1.0 "queues". There is however the possibility to also -configure AMQP 1.0 "topics" as well. In fact, the following formats for the `address` are -supported: -* `the-queue-name` (when configuring w/o prefix, the `address` is handled as "queue") -* `queue://the-queue-name` -* `topic://the-topic-name` +The common [target configuration](basic-connections.html#targets) applies. Target addresses support +these formats: -Further, `"topics"` is a list of strings, each list entry representing a subscription of -[Ditto protocol topics](protocol-specification-topic.html), see -[target topics and filtering](basic-connections.html#target-topics-and-filtering) for more information on that. - -Outbound messages are published to the configured target address if one of the subjects in `"authorizationContext"` -has READ permission on the thing, which is associated with a message. +* `the-queue-name` -- handled as a queue (default) +* `queue://the-queue-name` -- explicit queue +* `topic://the-topic-name` -- AMQP 1.0 topic ```json { - "address": "", + "address": "events/twin", "topics": [ "_/_/things/twin/events", "_/_/things/live/messages" ], - "authorizationContext": ["ditto:outbound-auth-subject", "..."] + "authorizationContext": ["ditto:outbound-auth-subject"] } ``` -#### Target acknowledgement handling - -For AMQP 1.0 targets, when configuring -[automatically issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label), requested -acknowledgements are produced in the following way: - -Once the AMQP 1.0 client signals that the message was acknowledged by the AMQP 1.0 endpoint, the following information -is mapped to the automatically created [acknowledgement](protocol-specification-acks.html#acknowledgement): +The target address supports [placeholders](basic-connections.html#placeholder-for-target-addresses). -* Acknowledgement.status: - * will be `200`, if the message was successfully consumed by the AMQP 1.0 endpoint - * will be `5xx`, if the AMQP 1.0 endpoint failed in consuming the message, retrying sending the message is feasible -* Acknowledgement.value: - * will be missing, for status `200` - * will contain more information, in case that an error `status` was set +### Target acknowledgement handling +When you configure [issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label): -### Specific configuration properties +| Status | Condition | +|--------|-----------| +| `200` | Message successfully consumed by the endpoint | +| `5xx` | Endpoint failed to consume the message (retry feasible) | -The specific configuration properties are interpreted as -[JMS Configuration options](https://qpid.apache.org/releases/qpid-jms-0.40.0/docs/index.html#jms-configuration-options). -Use these to customize and tweak your connection as needed. +## Specific configuration options +AMQP 1.0 specific configuration properties are interpreted as +[JMS Configuration options](https://qpid.apache.org/releases/qpid-jms-0.40.0/docs/index.html#jms-configuration-options). ### HMAC request signing -Ditto supports HMAC request signing for AMQP 1.0 connections. Find detailed information on this in -[Connectivity API > HMAC request signing](connectivity-hmac-signing.html). +Ditto supports HMAC request signing for AMQP 1.0 connections. See +[HMAC request signing](connectivity-hmac-signing.html) for details. - -## Establishing connecting to an AMQP 1.0 endpoint - -Ditto's [Connectivity service](architecture-services-connectivity.html) is responsible for creating new and managing -existing connections. - -This can be done dynamically at runtime without the need to restart any microservice using a -[Ditto DevOps command](installation-operating.html#devops-commands). - -Example connection configuration to create a new AMQP 1.0 connection: +## Example connection JSON ```json { @@ -204,22 +155,22 @@ Example connection configuration to create a new AMQP 1.0 connection: "connectionStatus": "open", "failoverEnabled": true, "uri": "amqps://user:password@hono.eclipse.org:5671", - "sources": [ - { - "addresses": [ - "telemetry/FOO" - ], - "authorizationContext": ["ditto:inbound-auth-subject"] - } - ], - "targets": [ - { - "address": "events/twin", - "topics": [ - "_/_/things/twin/events" - ], - "authorizationContext": ["ditto:outbound-auth-subject"] - } - ] + "sources": [{ + "addresses": ["telemetry/FOO"], + "authorizationContext": ["ditto:inbound-auth-subject"] + }], + "targets": [{ + "address": "events/twin", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"] + }] } ``` + +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Payload mapping](connectivity-mapping.html) -- transform message payloads +* [Header mapping](connectivity-header-mapping.html) -- map external headers +* [HMAC signing](connectivity-hmac-signing.html) -- HMAC-based authentication +* [TLS certificates](connectivity-tls-certificates.html) -- secure connections with TLS diff --git a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-hono.md b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-hono.md index 522c9d5cf7b..4114d705fa2 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-hono.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-hono.md @@ -5,75 +5,71 @@ tags: [connectivity] permalink: connectivity-protocol-bindings-hono.html --- -Consume messages from Eclipse Hono through Apache Kafka brokers and send messages to -Eclipse Hono in the same manner as the [Kafka connection](connectivity-protocol-bindings-kafka2.html) does. +You use the Eclipse Hono binding to integrate Ditto with Eclipse Hono without manually configuring Kafka topics, header mappings, or SASL settings. -The Hono connection type is implemented just for convenience - to avoid the need for the user to be aware of the specific -header mappings, address formats and Kafka `specificConfig` settings, which are required to connect to Eclipse Hono. -These specifics are applied automatically at runtime for the connections of type Hono. +{% include callout.html content="**TL;DR**: Configure a Hono connection with `connectionType: \"hono\"`. Source addresses use aliases (`event`, `telemetry`, `command_response`) that resolve to Kafka topics automatically. One Hono connection maps to one Hono tenant." type="primary" %} -The Hono connection is based on the Kafka connection and uses it behind the scenes, so most of the -[Kafka connection documentation](connectivity-protocol-bindings-kafka2.html) is valid for the Hono connection too, -but with some exceptions, as described below. +## Overview +The Hono connection type is a convenience wrapper around the +[Kafka connection](connectivity-protocol-bindings-kafka2.html). It automatically applies the +correct header mappings, address formats, and Kafka `specificConfig` settings required to +communicate with Eclipse Hono. + +Most of the [Kafka connection documentation](connectivity-protocol-bindings-kafka2.html) applies, +with the exceptions described below. {% include note.html - content="A Hono connection is associated with _one_ Hono tenant. That means for each Hono tenant a separate Hono connection - needs to be created. The tenant ID is used in the source and target connection addresses, representing the Kafka - topics used by Hono for sending and receiving messages for this tenant. See below sections [Source addresses](#source-addresses), - [Source reply target](#source-reply-target) and [Target Address](#target-address). The Hono tenant ID for the connection is defined in the - [specific config](#specific-configuration-properties) `honoTenantId` property." + content="A Hono connection is associated with _one_ Hono tenant. Create a separate connection for + each tenant. The tenant ID is configured via `specificConfig.honoTenantId`." %} -## Specific Hono connection configuration +## Connection URI format + +You do **not** specify the `uri` in a Hono connection -- it is generated automatically from the +connectivity service configuration: + +* `ditto.connectivity.hono.base-uri` -- protocol, host, and port +* `ditto.connectivity.hono.username` -- Kafka username +* `ditto.connectivity.hono.password` -- Kafka password + +The resulting URI follows the format `tcp://username:password@host:port`. -### Connection URI -In the Hono connection definition, the `uri` property should not be specified (any specified value will be ignored). -The connection URI and credentials are common for all Hono connections and are derived from the [configuration](installation-operating.html#ditto-configuration) of the connectivity service. -`uri` will be automatically generated, based on the values of 3 configuration properties of the connectivity service - -`ditto.connectivity.hono.base-uri`, `ditto.connectivity.hono.username` and `ditto.connectivity.hono.password`. -The connectivity service property `base-uri` must specify protocol, host and port number (see the [example below](#connectivity-configuration-example)). -In order to connect to Kafka brokers, `username` and `password` values will be inserted at runtime between the -protocol identifier and the host name parts of `base-uri`, resulting in a connection URI of the form `tcp://username:password@host.name:port`. +You must restart the service after changing these properties. -Note: If any of these parameters has to be changed, the service must be restarted to apply the new values. +## Source configuration -### Source format -#### Source addresses -For a Hono connection, source "addresses" are specified as aliases, which are resolved at runtime to Kafka topics to subscribe to. -Valid source addresses (aliases) are `event`, `telemetry` and `command_response`. -At runtime, these are resolved as follows: -* `event` -> `{%raw%}hono.event.{%endraw%}` -* `telemetry` -> `{%raw%}hono.telemetry.{%endraw%}` -* `command_response` -> `{%raw%}hono.command_response.{%endraw%}` +### Source addresses -`{%raw%}{%endraw%}` will be replaced by the value of `specificConfig.honoTenantId` or, if not set, -by the connection id. +Source `addresses` use aliases that resolve to Kafka topics at runtime: -#### Source reply target -Similar to source addresses, the reply target `address` is an alias as well. The single valid value for it is `command`. -It is resolved to Kafka topic/key like this: -* `command` -> `{%raw%}hono.command./{%endraw%}` +| Alias | Resolved Kafka topic | +|-------|---------------------| +| `event` | `hono.event.` | +| `telemetry` | `hono.telemetry.` | +| `command_response` | `hono.command_response.` | -`{%raw%}{%endraw%}` will be replaced by the value of `specificConfig.honoTenantId` or, if not set, -by the connection id. `{%raw%}{%endraw%}` is substituted by the thing ID value. +If `specificConfig.honoTenantId` is not set, the connection ID is used instead. -The needed header mappings for the `replyTarget` are also populated automatically at runtime and there is -no need to specify them in the connection definition. Any of the following specified value will be substituted (i.e. ignored). -Actually the `headerMapping` subsection is not required and could be omitted completely (in the context of `replyTarget`). +### Source reply target + +The reply target `address` alias `command` resolves to: + +* `hono.command./` + +Header mappings for the reply target are auto-generated. For `telemetry` and `event` sources: -For addresses `telemetry` and `event`, the following header mappings will be automatically applied: * `device_id`: `{%raw%}{{ thing:id }}{%endraw%}` -* `subject`: `{%raw%}{{ header:subject \| fn:default(topic:action-subject) \| fn:default(topic:criterion) }}{%endraw%}-response` +* `subject`: `{%raw%}{{ header:subject | fn:default(topic:action-subject) | fn:default(topic:criterion) }}{%endraw%}-response` * `correlation-id`: `{%raw%}{{ header:correlation-id }}{%endraw%}` -For address `command_response`, the following header mappings will be automatically applied: +For `command_response` sources: + * `correlation-id`: `{%raw%}{{ header:correlation-id }}{%endraw%}` * `status`: `{%raw%}{{ header:status }}{%endraw%}` -Note: Any other header mappings defined manually will be merged with the auto-generated ones. +Any additional manually defined header mappings are merged with the auto-generated ones. -The following example shows a valid Hono-connection source: ```json { "addresses": ["event"], @@ -97,34 +93,32 @@ The following example shows a valid Hono-connection source: "declaredAcks": [] } ``` -#### Source header mapping - -The Hono connection does not need any header mapping for sources. Nevertheless, the header mappings documented for -[Kafka connection](connectivity-protocol-bindings-kafka2.html) are still available. -See [Source header mapping](connectivity-protocol-bindings-kafka2.html#source-header-mapping) in Kafka protocol bindings -and [Header mapping for connections](connectivity-header-mapping.html). - -### Target format -#### Target address -The target `address` is specified as an alias and the only valid alias is `command`. -It is automatically resolved at runtime to the following Kafka topic/key: -* `command` -> `{%raw%}hono.command./{%endraw%}` - -`{%raw%}{%endraw%}` will be replaced by the value of `specificConfig.honoTenantId` or, if not set, -by the connection id. `{%raw%}{%endraw%}` is substituted by the thing ID value. - -#### Target header mapping -The target `headerMapping` section is also populated automatically at runtime and there is -no need to specify it the connection definition i.e. could be omitted. -If any of the following keys are specified in the connection, they will be ignored and automatically substituted as follows: + +### Source header mapping + +No source header mappings are required for Hono connections. The header mappings documented for +[Kafka connections](connectivity-protocol-bindings-kafka2.html#source-header-mapping) are still +available if needed. + +## Target configuration + +### Target address + +The only valid target `address` alias is `command`, which resolves to: + +* `hono.command./` + +### Target header mapping + +Target header mappings are auto-generated: + * `device_id`: `{%raw%}{{ thing:id }}{%endraw%}` -* `subject`: `{%raw%}{{ header:subject \| fn:default(topic:action-subject) }}{%endraw%}` +* `subject`: `{%raw%}{{ header:subject | fn:default(topic:action-subject) }}{%endraw%}` * `response-required`: `{%raw%}{{ header:response-required }}{%endraw%}` * `correlation-id`: `{%raw%}{{ header:correlation-id }}{%endraw%}` -Note: Any other header mappings defined manually will be merged with the auto-generated ones. +Additional manually defined mappings are merged with the auto-generated ones. -The following example shows a valid Hono-connection target: ```json { "address": "command", @@ -136,78 +130,68 @@ The following example shows a valid Hono-connection target: } ``` -### Specific configuration properties +## Specific configuration options -In the `specificConfig` section, the Hono tenant of the connection is specified in the `honoTenantId` property. -If that property is not set, the connection ID will be taken as Hono tenant ID. +| Property | Description | Default | +|----------|-------------|---------| +| `honoTenantId` | Hono tenant ID for this connection | connection ID | +| `groupId` | Kafka consumer group ID | connection ID | +| `debugEnabled` | Include debug information in acknowledgements | `false` | -The following Kafka connection related properties in the `specificConfig` section will be automatically added at runtime -to the connection. Any manually specified definition of `bootstrapServers` and `saslMechanism` will be ignored, but `groupId` will not. -* `bootstrapServers` The value will be taken from the configuration property `ditto.connectivity.hono.bootstrap-servers` of the connectivity service. -It must contain a comma separated list of Kafka bootstrap servers to use for connecting to (in addition to the automatically added connection uri). -* `saslMechanism` The value will be taken from configuration property `ditto.connectivity.hono.sasl-mechanism`. -The value must be one of `SaslMechanism` enum values to select the SASL mechanisms to use for authentication at Kafka: - * `PLAIN` - * `SCRAM-SHA-256` - * `SCRAM-SHA-512` -* `groupId`: could be specified by the user, but is not required. If omitted, the value of the connection ID will be automatically used. +The following properties are set automatically from the connectivity service configuration and +cannot be overridden: -Hono connection still allows to manually specify additional properties (like `debugEnabled`), which will be merged with the auto-generated ones. +* `bootstrapServers` -- from `ditto.connectivity.hono.bootstrap-servers` +* `saslMechanism` -- from `ditto.connectivity.hono.sasl-mechanism` -### Certificate validation -The connection property `validateCertificates` is also set automatically. The value is taken from the `ditto.connectivity.hono.validate-certificates` property. -For more details see [Connection configuration](connectivity-tls-certificates.html). +The `validateCertificates` connection property is also set automatically from +`ditto.connectivity.hono.validate-certificates`. + +## Example connection JSON -## Examples -### Example of Hono connection ```json { - "connection": { - "id": "connection-for-hono-example-tenant", - "connectionType": "hono", - "connectionStatus": "open", - "failoverEnabled": true, - "specificConfig": { - "honoTenantId": "example-tenant" + "id": "connection-for-hono-example-tenant", + "connectionType": "hono", + "connectionStatus": "open", + "failoverEnabled": true, + "specificConfig": { + "honoTenantId": "example-tenant" + }, + "sources": [{ + "addresses": ["event"], + "consumerCount": 1, + "qos": 1, + "authorizationContext": ["ditto:inbound-auth-subject"], + "enforcement": { + "input": "{%raw%}{{ header:device_id }}{%endraw%}", + "filters": ["{%raw%}{{ entity:id }}{%endraw%}"] }, - "sources": [ - { - "addresses": ["event"], - "consumerCount": 1, - "qos": 1, - "authorizationContext": ["ditto:inbound-auth-subject"], - "enforcement": { - "input": "{%raw%}{{ header:device_id }}{%endraw%}", - "filters": ["{%raw%}{{ entity:id }}{%endraw%}"] - }, - "headerMapping": {}, - "payloadMapping": ["Ditto"], - "replyTarget": { - "enabled": true, - "address": "command", - "expectedResponseTypes": ["response", "error", "nack"] - }, - "acknowledgementRequests": { - "includes": [] - }, - "declaredAcks": [] - } + "headerMapping": {}, + "payloadMapping": ["Ditto"], + "replyTarget": { + "enabled": true, + "address": "command", + "expectedResponseTypes": ["response", "error", "nack"] + }, + "acknowledgementRequests": { + "includes": [] + }, + "declaredAcks": [] + }], + "targets": [{ + "address": "command", + "topics": [ + "_/_/things/twin/events", + "_/_/things/live/messages" ], - "targets": [ - { - "address": "command", - "topics": [ - "_/_/things/twin/events", - "_/_/things/live/messages" - ], - "authorizationContext": ["ditto:outbound-auth-subject"] - } - ] - } + "authorizationContext": ["ditto:outbound-auth-subject"] + }] } ``` -### Connectivity configuration example -Here is an example with all the configuration options of the connectivity service that are needed by Hono connections: + +### Connectivity service configuration example + ``` ditto { connection { @@ -217,13 +201,21 @@ ditto { password = "honoPassword" sasl-mechanism = "PLAIN" bootstrap-servers = "localhost:9092" - validateCertificates = true, + validateCertificates = true ca = "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----" } } } ``` -## Troubleshooting Hono connection configuration -To help the troubleshooting, a separate Piggyback command `retrieveHonoConnection` is implemented. -It is valid only for Hono connections. It returns the "real" Hono connection after all its properties being resolved or auto-generated. -The returned value could be used for inspection, but not for example to create a new Hono connection using it. + +## Troubleshooting + +Use the `retrieveHonoConnection` piggyback command to inspect the fully resolved Hono connection +with all auto-generated properties. This is useful for debugging but the output cannot be used +directly to create a new connection. + +## Further reading + +* [Kafka 2.x binding](connectivity-protocol-bindings-kafka2.html) -- underlying Kafka connection details +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Payload mapping](connectivity-mapping.html) -- transform message payloads diff --git a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-http.md b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-http.md index 2ac270c3816..24dd6155208 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-http.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-http.md @@ -5,50 +5,47 @@ tags: [protocol, connectivity, rql] permalink: connectivity-protocol-bindings-http.html --- -Perform HTTP request (with verbs GET, POST, PUT, PATCH) to HTTP endpoints via [targets](#target-format). +You use the HTTP 1.1 binding to push data from Ditto to external HTTP endpoints using GET, POST, PUT, or PATCH requests. -## Specific connection configuration +{% include callout.html content="**TL;DR**: Configure an HTTP connection with `connectionType: \"http-push\"`. Target addresses use the format `VERB:/path`. HTTP connections are outbound-only -- they do not support sources." type="primary" %} -The common configuration for connections in [Connections > Targets](basic-connections.html#targets) applies here -as well. Following are some specifics for HTTP connections: +## Overview -### Source format +The HTTP 1.1 protocol binding lets you perform HTTP requests to external endpoints via +[targets](#target-configuration). This is useful for forwarding events, sending messages, +or integrating with REST APIs. -{% include note.html content="HTTP connections currently don't support sources - the HTTP integration may only be used for pushing data out." %} +{% include note.html content="HTTP connections do not support sources -- the HTTP integration is outbound-only." %} -### Target format +## Connection URI format -A HTTP connection requires the protocol configuration target object to have an `address` property. -This property has the following format: `:` +``` +http://user:password@hostname:80 +``` -The supported HTTP `` values are: -* GET -* POST -* PUT -* PATCH +Use `https://` for TLS-secured connections. -The specified `` contains the path - including optionally potential query parameters - to be appended to the -configured `uri` of the connection. +## Source configuration -The body of the HTTP request is either the outgoing [Ditto Protocol](protocol-specification.html) message (e.g. an event) -or - if a [payload mapping](connectivity-mapping.html) was specified in the connection - a transformed body. +HTTP connections do not support sources. -The target address may contain placeholders; see -[placeholders](basic-connections.html#placeholder-for-target-addresses) section for more information. +## Target configuration -The target may define a [header mapping](connectivity-header-mapping.html) specifying which additional HTTP headers to -send along with the performed HTTP requests. +The common [target configuration](basic-connections.html#targets) applies. The target `address` +combines an HTTP verb and path: -Further, `"topics"` is a list of strings, each list entry representing a subscription of -[Ditto protocol topics](protocol-specification-topic.html), see -[target topics and filtering](basic-connections.html#target-topics-and-filtering) for more information on that. +``` +: +``` -Outbound messages are published to the configured target address if one of the subjects in `"authorizationContext"` -has READ permission on the thing, which is associated with a message. +Supported HTTP verbs: `GET`, `POST`, `PUT`, `PATCH`. + +The `` is appended to the connection `uri`. It can include query parameters and +[placeholders](basic-connections.html#placeholder-for-target-addresses). ```json { - "address": ":", + "address": "PUT:/api/2/some-entity/{%raw%}{{ thing:id }}{%endraw%}", "topics": [ "_/_/things/twin/events", "_/_/things/live/messages" @@ -60,201 +57,138 @@ has READ permission on the thing, which is associated with a message. } ``` -#### Target header mapping -HTTP 1.1 connections supports specific header mapping. - -The following header have a special meaning in that the values are applied directly to the published message: -* `http.query`: sets the value of this header as query parameter -* `http.path`: sets the value of this header as the path of the HTTP request - -#### Target acknowledgement handling - -For HTTP targets, whenever a message is published to the HTTP endpoint, you have two different options in order to -acknowledge receiving the message: - -##### Explicitly responding with Ditto Protocol acknowledgement message - -Whenever an HTTP endpoint, which received a message -[requesting acknowledgements](basic-acknowledgements.html#requesting-acks), -responds with a [Ditto Protocol Acknowledgement](protocol-specification-acks.html#acknowledgement) and sets the -`Content-Type` header of the HTTP response to `application/vnd.eclipse.ditto+json`, this received message is treated -as custom [acknowledgement](basic-acknowledgements.html). - -This however is only the case if no -[automatically issued acknowledgement label](basic-connections.html#target-issued-acknowledgement-label) was configured -for that target (see section below). If such an issued acknowledgement label was configured, this one always gets -issued instead of a custom sent back Ditto Protocol Acknowledgement. - -##### Implicitly create acknowledgement from HTTP response - -When for the target an -[automatically issued acknowledgement label](basic-connections.html#target-issued-acknowledgement-label) was configured -and the HTTP response was not a Ditto Protocol message (with Content-Type header `application/vnd.eclipse.ditto+json`), -an acknowledgement is produced automatically in the following way: +### Target header mapping -The HTTP response and following HTTP response information is mapped to the -automatically created [acknowledgement](protocol-specification-acks.html#acknowledgement): -* `Acknowledgement.headers`: the HTTP response headers are added. -* `Acknowledgement.status`: the HTTP response status code is used. -* `Acknowledgement.value`: the HTTP response body is used - if the response body was of - `content-type: application/json`, the JSON is inlined into the acknowledgement, otherwise the payload is added as - JSON string. - -#### Responding to messages +HTTP connections support full [header mapping](connectivity-header-mapping.html). These special +headers control the HTTP request directly: -For [live messages](basic-messages.html) that are published via an HTTP target you have two different options to -respond to that message: +| Header | Effect | +|--------|--------| +| `http.query` | Sets query parameters on the HTTP request | +| `http.path` | Sets the path of the HTTP request | -##### Explicitly responding with Ditto Protocol message response +### Target acknowledgement handling -Whenever an HTTP endpoint, which received a [live message](basic-messages.html), -responds with a [Ditto Protocol Message Response](protocol-specification-things-messages.html#responding-to-a-message) -and sets the `Content-Type` header of the HTTP response to `application/vnd.eclipse.ditto+json`, this received message -is treated as custom [live message response](basic-messages.html#responding-to-messages). +You have two options for acknowledging messages sent to HTTP endpoints: -In this case, the `correlation-id`, `thing-id` and potentially `feature-id` of the response have to match the -message properties to respond to. +#### Explicit Ditto Protocol acknowledgement -##### Implicitly responding via HTTP response +If the HTTP endpoint responds with a [Ditto Protocol Acknowledgement](protocol-specification-acks.html#acknowledgement) +and sets `Content-Type: application/vnd.eclipse.ditto+json`, Ditto treats the response as a custom +acknowledgement. This only works if no [issued acknowledgement label](basic-connections.html#target-issued-acknowledgement-label) is configured. -When for the target an -[automatically issued acknowledgement label](basic-connections.html#target-issued-acknowledgement-label) with the label -`live-response` was configured and the HTTP response was not a Ditto Protocol message -(with Content-Type header `application/vnd.eclipse.ditto+json`), a message response is produced automatically in the -following way: +#### Implicit acknowledgement from HTTP response -The HTTP response and following HTTP response information is mapped to the -automatically created [message response](protocol-specification-things-messages.html#responding-to-a-message): -* `Message.headers`: the HTTP response headers are added. -* `Message.status`: the HTTP response status code is used. -* `Message.value`: the HTTP response body is used - if the response body was of - `content-type: application/json`, the JSON is inlined into the acknowledgement, otherwise the payload is added as - JSON string. +When an [issued acknowledgement label](basic-connections.html#target-issued-acknowledgement-label) +is configured, the HTTP response is automatically mapped to an acknowledgement: +* `Acknowledgement.status` -- the HTTP response status code +* `Acknowledgement.headers` -- the HTTP response headers +* `Acknowledgement.value` -- the HTTP response body (inlined as JSON if `application/json`, otherwise as a string) -### Specific configuration properties +### Responding to live messages -The specific configuration properties contain the following optional keys: -* `parallelism` (optional): Configures how many parallel requests per connection to perform, each takes one outgoing -TCP connection. Default (if not provided): 1 -* `omitRequestBody` (optional): Configures for which HTTP methods, provided as a comma separated list, the request -body is omitted for requests made via this connection. Default (if not provided): `GET,DELETE`. Leave empty to -always send the request body. +For [live messages](basic-messages.html) published via HTTP targets, you can respond in two ways: -## Establishing connecting to an HTTP endpoint +1. **Explicit** -- respond with a [Ditto Protocol Message Response](protocol-specification-things-messages.html#responding-to-a-message) + with `Content-Type: application/vnd.eclipse.ditto+json`. The `correlation-id`, `thing-id`, and + potentially `feature-id` of the response must match the original message. +2. **Implicit** -- configure the + [issued acknowledgement label](basic-connections.html#target-issued-acknowledgement-label) `live-response`, + and the HTTP response is automatically converted to a message response when the response is not + a Ditto Protocol message (i.e., not `application/vnd.eclipse.ditto+json`). The HTTP response is + mapped as follows: + * `Message.headers` -- the HTTP response headers + * `Message.status` -- the HTTP response status code + * `Message.value` -- the HTTP response body (inlined as JSON if `application/json`, otherwise as a string) -Ditto's [Connectivity service](architecture-services-connectivity.html) is responsible for creating new and managing -existing connections. +## Specific configuration options -This can be done dynamically at runtime without the need to restart any microservice using a -[Ditto DevOps command](installation-operating.html#devops-commands). +| Property | Description | Default | +|----------|-------------|---------| +| `parallelism` | Number of parallel HTTP requests per connection | `1` | +| `omitRequestBody` | HTTP methods for which the request body is omitted (comma-separated) | `GET,DELETE` | -Example connection configuration to create a new HTTP connection in order to make request to an HTTP endpoint: +## Example connection JSON ```json { - "connection": { - "id": "http-example-connection-123", - "connectionType": "http-push", - "connectionStatus": "open", - "failoverEnabled": true, - "uri": "http://user:password@localhost:80", - "specificConfig": { - "parallelism": "2" - }, - "sources": [], - "targets": [ - { - "address": "PUT:/api/2/some-entity/{%raw%}{{ thing:id }}{%endraw%}", - "topics": [ - "_/_/things/twin/events" - ], - "authorizationContext": ["ditto:outbound-auth-subject", "..."], - "headerMapping": { - "content-type": "{%raw%}{{ header:content-type }}{%endraw%}", - "api-key": "this-is-a-secret-api-key-to-send-along" - } - } - ] - } + "id": "http-example-connection-123", + "connectionType": "http-push", + "connectionStatus": "open", + "failoverEnabled": true, + "uri": "http://user:password@localhost:80", + "specificConfig": { + "parallelism": "2" + }, + "sources": [], + "targets": [{ + "address": "PUT:/api/2/some-entity/{%raw%}{{ thing:id }}{%endraw%}", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], + "headerMapping": { + "content-type": "{%raw%}{{ header:content-type }}{%endraw%}", + "api-key": "this-is-a-secret-api-key" + } + }] } ``` ### Client-certificate authentication -Ditto supports certificate-based authentication for HTTP connections. Consult -[Certificates for Transport Layer Security](connectivity-tls-certificates.html) -for how to set it up. - -Here is an example HTTP connection that checks the server certificate and authenticates by a client certificate. +Ditto supports certificate-based authentication for HTTP connections. See +[TLS certificates](connectivity-tls-certificates.html) for setup instructions. ```json { - "connection": { - "id": "http-example-connection-123", - "connectionType": "http-push", - "connectionStatus": "open", - "failoverEnabled": true, - "uri": "https://localhost:443", - "validateCertificates": true, - "ca": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", - "credentials": { - "type": "client-cert", - "cert": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", - "key": "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----" - }, - "specificConfig": { - "parallelism": "2" - }, - "sources": [], - "targets": [ - { - "address": "PUT:/api/2/some-entity/{%raw%}{{ thing:id }}{%endraw%}", - "topics": [ - "_/_/things/twin/events" - ], - "authorizationContext": ["ditto:outbound-auth-subject", "..."], - "headerMapping": { - "content-type": "{%raw%}{{ header:content-type }}{%endraw%}", - "api-key": "this-is-a-secret-api-key-to-send-along" - } - } - ] - } + "id": "http-example-connection-123", + "connectionType": "http-push", + "connectionStatus": "open", + "failoverEnabled": true, + "uri": "https://localhost:443", + "validateCertificates": true, + "ca": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", + "credentials": { + "type": "client-cert", + "cert": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", + "key": "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----" + }, + "specificConfig": { + "parallelism": "2" + }, + "sources": [], + "targets": [{ + "address": "PUT:/api/2/some-entity/{%raw%}{{ thing:id }}{%endraw%}", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], + "headerMapping": { + "content-type": "{%raw%}{{ header:content-type }}{%endraw%}" + } + }] } ``` ### HMAC request signing -Ditto supports HMAC request signing for HTTP push connections. Find detailed information on this in -[Connectivity API > HMAC request signing](connectivity-hmac-signing.html). +Ditto supports HMAC request signing for HTTP push connections. See +[HMAC request signing](connectivity-hmac-signing.html) for details. ### OAuth2 authentication -In the subsections (either using [client credentials flow](#client-certificate-authentication) or -[password flow](#oauth2-password-flow)), ach HTTP request to `https://localhost:443/event-publication` includes a -bearer token issued by `https://localhost:443/oauth2/token`. -The HTTP connection will obtain a new token before the old token expires according to a configured `max-clock-skew`. -To prevent looping access token requests, each token is used once even if -the token endpoint responds with expired tokens. Rejected or malformed access token responses are considered -misconfiguration errors. +HTTP connections support OAuth2 for obtaining bearer tokens. Each request includes a token issued +by the configured token endpoint. Ditto obtains a new token before the old one expires. + +You can configure `max-clock-skew` and HTTPS enforcement in `connectivity-extension.conf`: -It is possible to configure `max-clock-skew` and whether to enforce HTTPS for token endpoints in -`connectivity-extension.conf` or by environment variables. ```hocon ditto { connectivity { connection { http-push { oauth2 { - # Maximum clock skew of OAuth2 token endpoints. - # Access tokens are refreshed this long before expiration. max-clock-skew = 60s max-clock-skew = ${?CONNECTIVITY_HTTP_OAUTH2_MAX_CLOCK_SKEW} - # Whether to enforce HTTPS for OAuth2 token endpoints. - # Should be `true` for production environments - # in order not to transmit client secrets in plain text. enforce-https = true enforce-https = ${?CONNECTIVITY_HTTP_OAUTH2_ENFORCE_HTTPS} } @@ -266,64 +200,43 @@ ditto { #### OAuth2 client credentials flow -HTTP push connections can authenticate themselves via OAuth2 client credentials flow as described in -[section 4.4 of RFC-6749](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4). -To configure OAuth2 credentials: -- Set `type` to `oauth-client-credentials` -- Set `tokenEndpoint` to the URI of the access token request endpoint -- Set `clientId` to the client ID to include in access token requests -- Set `clientSecret` to the client secret to include in access token requests -- Set `requestedScopes` to the scopes to request in access token requests +Authenticate via [RFC-6749 Section 4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4): -This is an example connection with OAuth2 credentials. ```json { - "connection": { - "id": "http-example-connection-124", - "connectionType": "http-push", - "connectionStatus": "open", - "uri": "https://localhost:443/event-publication", - "credentials": { - "type": "oauth-client-credentials", - "tokenEndpoint": "https://localhost:443/oauth2/token", - "clientId": "my-client-id", - "clientSecret": "my-client-secret", - "requestedScopes": "user-scope-1 role-scope-2" - }, - ... + "credentials": { + "type": "oauth-client-credentials", + "tokenEndpoint": "https://auth.example.com/oauth2/token", + "clientId": "my-client-id", + "clientSecret": "my-client-secret", + "requestedScopes": "scope-1 scope-2" + } } ``` #### OAuth2 password flow -HTTP push connections can authenticate themselves via OAuth2 password flow as described in -[section 4.3 of RFC-6749](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3). -To configure OAuth2 credentials: -- Set `type` to `oauth-password` -- Set `tokenEndpoint` to the URI of the access token request endpoint -- Set `clientId` to the client ID to include in access token requests -- Optionally: Set `clientSecret` to the client secret to include in access token requests (or leave away if the - client is public and does not have a secret) -- Set `requestedScopes` to the scopes to request in access token requests -- Set `username` to the username to use for obtaining the token -- Set `password` to the password to use for obtaining the token - -This is an example connection with OAuth2 credentials. +Authenticate via [RFC-6749 Section 4.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3). +Optionally set `clientSecret` to include a client secret in access token requests, or leave it out +if the client is public and does not have a secret: + ```json { - "connection": { - "id": "http-example-connection-124", - "connectionType": "http-push", - "connectionStatus": "open", - "uri": "https://localhost:443/event-publication", - "credentials": { - "type": "oauth-password", - "tokenEndpoint": "https://localhost:443/oauth2/token", - "clientId": "my-public-client-id", - "requestedScopes": "user-scope-1 role-scope-2", - "username": "my-username", - "password": "my-password" - }, - ... + "credentials": { + "type": "oauth-password", + "tokenEndpoint": "https://auth.example.com/oauth2/token", + "clientId": "my-public-client-id", + "requestedScopes": "scope-1 scope-2", + "username": "my-username", + "password": "my-password" + } } ``` + +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Payload mapping](connectivity-mapping.html) -- transform message payloads +* [Header mapping](connectivity-header-mapping.html) -- map external headers +* [HMAC signing](connectivity-hmac-signing.html) -- HMAC-based authentication +* [TLS certificates](connectivity-tls-certificates.html) -- secure connections with TLS diff --git a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-kafka2.md b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-kafka2.md index 30c67ebe4b2..a696389ac98 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-kafka2.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-kafka2.md @@ -5,45 +5,52 @@ tags: [protocol, connectivity, rql] permalink: connectivity-protocol-bindings-kafka2.html --- -Consume messages from Apache Kafka brokers via [sources](#source-format) and send messages to Apache Kafka brokers via -[targets](#target-format). +You use the Kafka 2.x binding to consume messages from and publish messages to Apache Kafka brokers. -## Content-type +{% include callout.html content="**TL;DR**: Configure a Kafka connection with `connectionType: \"kafka\"`. You must set `bootstrapServers` in `specificConfig`. Source addresses are Kafka topics, and target addresses support `topic`, `topic/key`, and `topic#partition` formats." type="primary" %} -When messages are sent in [Ditto Protocol](protocol-overview.html) (as `UTF-8` encoded String payload), -the `content-type` of Apache Kafka messages must be set to: +## Overview + +The Kafka 2.x protocol binding lets you consume messages from Kafka via +[sources](#source-configuration) and publish messages via [targets](#target-configuration). + +When you send messages in [Ditto Protocol](protocol-overview.html) format (`UTF-8` encoded strings), +set the `content-type` to: ``` application/vnd.eclipse.ditto+json ``` -If messages, which are not in Ditto Protocol, should be processed, a [payload mapping](connectivity-mapping.html) must -be configured for the connection in order to transform the messages. +For other formats, configure a [payload mapping](connectivity-mapping.html). + +### Global Kafka client configuration + +You can configure the Kafka client behavior in +[connectivity.conf](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf) +under `ditto.connectivity.connection.kafka`: -## Global Kafka client configuration +* `consumer` -- consumer settings for [sources](#source-configuration) +* `committer` -- commit batch size and interval +* `producer` -- producer settings for [targets](#target-configuration) + +## Connection URI format + +``` +tcp://user:password@hostname:9092 +``` -The behavior of the used Kafka client can be configured in the [connectivity.conf](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf) -under key `ditto.connectivity.connection.kafka`: -* `consumer`: The Kafka consumer configuration applied when configuring [sources](#source-format) in order to consume messages from Kafka -* `committer`: The Kafka committer configuration to apply when consuming messages, e.g. the `max-batch` size and `max-interval` duration -* `producer`: The Kafka producer configuration applied when configuring [targets](#target-format) in order to publish messages to Kafka +## Source configuration -## Specific connection configuration +The common [source configuration](basic-connections.html#sources) applies. Source `addresses` are +Kafka topics. Legal characters: `[a-z]`, `[A-Z]`, `[0-9]`, `.`, `_`, `-`. -The common configuration for connections in [Connections > Targets](basic-connections.html#targets) applies here -as well. Following are some specifics for Apache Kafka 2.x connections: +### Quality of Service -### Source format -For a Kafka connection source "addresses" are Kafka topics to subscribe to. Legal characters are `[a-z]`, `[A-Z]`, `[0-9]`, `.`, `_` and `-`. +The `qos` field controls message delivery semantics: -Messages are either consumed in an "at-most-once" or "at-least-once" manner depending on the -configured `"qos"` (Quality of Service) value of the source: -* `"qos": 0` (at-most-once): This means that the offset will be committed after Ditto consumed the message from Kafka, - no matter if the message could be processed or not. -* `"qos": 1` (at-least-once): This means that the offset will only be committed after - [requested acknowledgements](basic-acknowledgements.html#requesting-acks) were successfully issued. +* `qos: 0` (at-most-once) -- offsets are committed after consumption, regardless of processing success +* `qos: 1` (at-least-once) -- offsets are committed only after [requested acknowledgements](basic-acknowledgements.html#requesting-acks) succeed -The following example shows a valid Kafka source: ```json { "addresses": ["theTopic"], @@ -68,35 +75,23 @@ The following example shows a valid Kafka source: "declaredAcks": [] } ``` -#### Quality of Service -The shown example with the configured `"qos": 1` has the following behavior: -* Kafka messages from the topic `"theAddress"` are consumed in an "at-least-once" fashion, e.g. - [twin modify commands](basic-signals-command.html#modify-commands) will implicitly request the - [built-in acknowledgement label](basic-acknowledgements.html#built-in-acknowledgement-labels) `"twin-persisted"` meaning - that the consumed message will only be committed to Kafka after it was successfully persisted by Ditto -* When a consumed Kafka message could not be acknowledged by Ditto (e.g. because persisting a consumed command failed), - consuming from the Kafka source will be restarted which means that message consumption will restart from the last - committed offset of the Kafka topic, already successfully processed messages could be processed again as a result - (which is the "at-least-once" semantic). +When `qos: 1` is set, twin modify commands automatically request the `twin-persisted` +acknowledgement. If processing fails, consumption restarts from the last committed offset +(at-least-once semantics). -For Kafka sources, it is not possible to have different Quality of Service on a per message basis. -Either all messages from a source are consumed in an "at-most-once" or in an "at-least-once" semantic, depending on the -configured `"qos"` value. +### Source header mapping +Ditto extracts these headers from each consumed Kafka record: -#### Source header mapping +| Header | Description | +|--------|-------------| +| `kafka.topic` | Kafka topic the record was received from | +| `kafka.key` | Record key (if available) | +| `kafka.timestamp` | Record timestamp | -The Kafka protocol binding supports to map arbitrary headers from a consumed record to the message that is further -processed by Ditto (see [Header Mapping](connectivity-header-mapping.html)). +These headers can be used in a source header mapping: -In addition, there are three special headers extracted from every received record that can be used in a payload or -header mapping: -* `kafka.topic`: contains the Kafka topic the record was received from -* `kafka.key`: contains the key of the received record (only set if key is available) -* `kafka.timestamp`: contains the timestamp of the received record - -These headers may be used in a source header mapping: ```json { "headerMapping": { @@ -106,60 +101,48 @@ These headers may be used in a source header mapping: } ``` -#### Message expiry +All other Kafka record headers are also available for [header mapping](connectivity-header-mapping.html). + +### Message expiry -In the Ditto implementation for consuming messages from Kafka we also added a feature for message expiration. This way a device can express for how long a message is valid to be processed. -To use this feature, two headers are relevant: -* `creation-time`: Epoch millis value when the message was created. -* `ttl`: Number milliseconds the message should be considered as valid. +Devices can set message expiry using two headers: -When Ditto consumes such a message it checks whether the amount of milliseconds since `creation-time` is larger than specified by `ttl`. -If so, the message will be ignored. -If this is not the case or the headers are not specified at all, the message will be processed normally. +* `creation-time` -- epoch milliseconds when the message was created +* `ttl` -- milliseconds the message remains valid -#### Backpressure by using acknowledgements +Ditto drops expired messages (where elapsed time since `creation-time` exceeds `ttl`). -For Kafka Sources one can use [acknowledements](basic-acknowledgements.html) to achieve backpressure from the event/message consuming application down to the Kafka consumer in Ditto. -So if for example [live messages](basic-messages.html) should be consumed via the Kafka connection, you could want that the consume rate adapts to the performance of the message consuming and responding application. +### Backpressure via acknowledgements -For this scenario there is nothing that needs to be configured explicitly. Since the `live-response` is a built in acknowledgement, it is requested by default. -The same applies for [twin modify commands](basic-signals-command.html#modify-commands). -For those type of commands the `twin-persisted` acknowledgement is requested automatically which would cause backpressure from the persistence to the Kafka consumer. +For Kafka sources, you can use [acknowledgements](basic-acknowledgements.html) for backpressure. +Built-in acknowledgements like `live-response` and `twin-persisted` are requested by default +for their respective message types. -If for some reason you don't want to have this backpressure, because losing some messages due to for example overflowing buffers is not a problem for you, you can disable requesting acknowledgements for the Kafka source. -This can be done by configuring the following for your source: +To disable backpressure: ```json -"acknowledgementRequests": { - "includes": [], - "filter": "fn:delete()" +{ + "acknowledgementRequests": { + "includes": [], + "filter": "fn:delete()" + } } ``` -If you however want to achieve backpressure from an event consuming application to the Kafka consumer, you could use custom [acknowledgement requests](basic-acknowledgements.html#requesting-acks). - -### Target format - -A Kafka 2.x connection requires the protocol configuration target object to have an `address` property. -This property may have different formats: - -* `topic`: Contains a Kafka topic - a partition will be assigned in a round-robin fashion. -* `topic/key`: Contains a Kafka topic and a key - Kafka ensures that messages with the same key end up in the same partition. -* `topic#partitionNumber`: Contains a Kafka topic and a specific partition number - that partition will be used when sending records. +## Target configuration -The target address may contain placeholders; see -[placeholders](basic-connections.html#placeholder-for-target-addresses) section for more information. +The common [target configuration](basic-connections.html#targets) applies. Target `address` supports +these formats: -Further, `"topics"` is a list of strings, each list entry representing a subscription of -[Ditto protocol topics](protocol-specification-topic.html), see -[target topics and filtering](basic-connections.html#target-topics-and-filtering) for more information on that. - -Outbound messages are published to the configured target address if one of the subjects in `"authorizationContext"` -has READ permission on the thing, which is associated with a message. +| Format | Description | +|--------|-------------| +| `topic` | Round-robin partition assignment | +| `topic/key` | Key-based partitioning (same key = same partition) | +| `topic#partition` | Specific partition number | ```json { - "address": "/", + "address": "myTopic/myKey", "topics": [ "_/_/things/twin/events", "_/_/things/live/messages" @@ -168,100 +151,87 @@ has READ permission on the thing, which is associated with a message. } ``` -#### Target acknowledgement handling - -For Kafka targets, when configuring -[automatically issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label), requested -acknowledgements are produced in the following way: - -Once the Kafka client signals that the message was acknowledged by the Kafka broker, the following information is mapped -to the automatically created [acknowledement](protocol-specification-acks.html#acknowledgement): -* Acknowledgement.status: - * will be `204`, if Kafka debug mode was disabled and the message was successfully consumed by Kafka - * will be `200`, if Kafka debug mode was enabled (see [specific config](#specific-configuration-properties) `"debugEnabled"`) and the message was successfully consumed by Kafka - * will be `4xx`, if Kafka failed to consume the message but retrying sending the message does not make sense - * will be `5xx`, if Kafka failed to consume the message but retrying sending the message is feasible -* Acknowledgement.value: - * will be missing, if Kafka debug mode (see [specific config](#specific-configuration-properties) `"debugEnabled"`) was disabled - * will include the Kafka `RecordMetadata` as JsonObject: - * `timestamp` (if present) - * `serializedKeySize` - * `serializedValueSize` - * `topic` - * `partition` - * `offset` (if present) - -### Specific configuration properties - -The specific configuration properties contain the following keys: -* `bootstrapServers` (required): contains a comma separated list of Kafka bootstrap servers to use for connecting to -(in addition to the still required connection uri) -* `saslMechanism` (required if connection uri contains username\/password): contains one of the following SASL mechanisms to use for authentication at Kafka: - * `plain` - * `scram-sha-256` - * `scram-sha-512` -* `debugEnabled`: determines whether for acknowledgements - [automatically issued by Kafka targets](#target-acknowledgement-handling) additional debug information should be - included as payload or not - default: `false` -* `groupId`: The consumer group ID to be used by the kafka consumer. If not defined the group ID will be equal to the connection ID. - - -## Establishing connecting to an Apache Kafka endpoint - -Ditto's [Connectivity service](architecture-services-connectivity.html) is responsible for creating new and managing -existing connections. - -This can be done dynamically at runtime without the need to restart any microservice using a -[Ditto DevOps command](installation-operating.html#devops-commands). - -Example connection configuration to create a new Kafka 2.x connection in order to connect to a running Apache Kafka server: +The target address supports [placeholders](basic-connections.html#placeholder-for-target-addresses). + +### Target acknowledgement handling + +When you configure [issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label): + +| Status | Condition | +|--------|-----------| +| `204` | Message consumed successfully (debug mode disabled) | +| `200` | Message consumed successfully (debug mode enabled, includes `RecordMetadata`) | +| `4xx` | Kafka failed to consume (no retry) | +| `5xx` | Kafka failed to consume (retry feasible) | + +When debug mode is enabled (`"debugEnabled": "true"`), the `200` response includes the Kafka +`RecordMetadata` as a JSON object with these fields: + +* `timestamp` (if present) +* `serializedKeySize` +* `serializedValueSize` +* `topic` +* `partition` +* `offset` (if present) + +## Specific configuration options + +| Property | Description | Default | +|----------|-------------|---------| +| `bootstrapServers` (required) | Comma-separated list of Kafka bootstrap servers | -- | +| `saslMechanism` (required with auth) | SASL mechanism: `plain`, `scram-sha-256`, or `scram-sha-512` | -- | +| `debugEnabled` | Include Kafka `RecordMetadata` in acknowledgement payloads | `false` | +| `groupId` | Consumer group ID | connection ID | + +## Example connection JSON ```json { - "connection": { - "id": "kafka-example-connection-123", - "connectionType": "kafka", - "connectionStatus": "open", - "failoverEnabled": true, - "uri": "tcp://user:password@localhost:9092", - "specificConfig": { - "bootstrapServers": "localhost:9092,other.host:9092", - "saslMechanism": "plain" + "id": "kafka-example-connection-123", + "connectionType": "kafka", + "connectionStatus": "open", + "failoverEnabled": true, + "uri": "tcp://user:password@localhost:9092", + "specificConfig": { + "bootstrapServers": "localhost:9092,other.host:9092", + "saslMechanism": "plain" + }, + "sources": [{ + "addresses": ["theTopic"], + "consumerCount": 1, + "qos": 1, + "authorizationContext": ["ditto:inbound-auth-subject"], + "enforcement": { + "input": "{%raw%}{{ header:device_id }}{%endraw%}", + "filters": ["{%raw%}{{ entity:id }}{%endraw%}"] + }, + "headerMapping": {}, + "payloadMapping": ["Ditto"], + "replyTarget": { + "enabled": true, + "address": "theReplyTopic", + "headerMapping": {}, + "expectedResponseTypes": ["response", "error", "nack"] }, - "sources": [ - { - "addresses": ["theTopic"], - "consumerCount": 1, - "qos": 1, - "authorizationContext": ["ditto:inbound-auth-subject"], - "enforcement": { - "input": "{%raw%}{{ header:device_id }}{%endraw%}", - "filters": ["{%raw%}{{ entity:id }}{%endraw%}"] - }, - "headerMapping": {}, - "payloadMapping": ["Ditto"], - "replyTarget": { - "enabled": true, - "address": "theReplyTopic", - "headerMapping": {}, - "expectedResponseTypes": ["response", "error", "nack"] - }, - "acknowledgementRequests": { - "includes": [] - }, - "declaredAcks": [] - } + "acknowledgementRequests": { + "includes": [] + }, + "declaredAcks": [] + }], + "targets": [{ + "address": "topic/key", + "topics": [ + "_/_/things/twin/events", + "_/_/things/live/messages" ], - "targets": [ - { - "address": "topic/key", - "topics": [ - "_/_/things/twin/events", - "_/_/things/live/messages" - ], - "authorizationContext": ["ditto:outbound-auth-subject"] - } - ] - } + "authorizationContext": ["ditto:outbound-auth-subject"] + }] } ``` + +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Payload mapping](connectivity-mapping.html) -- transform message payloads +* [Header mapping](connectivity-header-mapping.html) -- map external headers +* [TLS certificates](connectivity-tls-certificates.html) -- secure connections with TLS diff --git a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-mqtt.md b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-mqtt.md index edf92788859..a8b3c22c95a 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-mqtt.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-mqtt.md @@ -5,70 +5,63 @@ tags: [protocol, connectivity] permalink: connectivity-protocol-bindings-mqtt.html --- -Consume messages from MQTT brokers via [sources](#source-format) and send messages to MQTT brokers via -[targets](#target-format). +You use the MQTT 3.1.1 binding to connect Ditto with MQTT brokers for lightweight, publish-subscribe messaging. -## Content-type +{% include callout.html content="**TL;DR**: Configure an MQTT 3.1.1 connection with `connectionType: \"mqtt\"`. Source addresses are MQTT topics (wildcards `+` and `#` allowed). Set the `qos` field on both sources and targets." type="primary" %} -When MQTT messages are sent in [Ditto Protocol](protocol-overview.html), -the payload should be `UTF-8` encoded strings. +## Overview -If messages, which are not in Ditto Protocol, should be processed, a [payload mapping](connectivity-mapping.html) must -be configured for the connection in order to transform the messages. +The MQTT 3.1.1 protocol binding lets you consume messages from MQTT brokers via +[sources](#source-configuration) and publish messages via [targets](#target-configuration). -## MQTT 3.1.1 properties +MQTT payloads should be `UTF-8` encoded strings when sent in [Ditto Protocol](protocol-overview.html) +format. For other formats, configure a [payload mapping](connectivity-mapping.html). -MQTT 3.1.1 messages have no application headers. Transmission-relevant properties are set in the -`"headers"` field as a part of [Ditto protocol messages](protocol-specification.html#dittoProtocolEnvelope) in the -payload. +### MQTT 3.1.1 properties -This property is supported: +MQTT 3.1.1 has no application headers. Set transmission-relevant properties in the `"headers"` +field of the [Ditto protocol message](protocol-specification.html#dittoProtocolEnvelope) payload. -* `correlation-id`: For correlating request messages and events. Twin events have the correlation IDs of - [Twin commands](protocol-twinlive.html#twin) that produced them. +Supported property: -## Specific connection configuration +* `correlation-id` -- correlates request messages and events -The common configuration for connections in [Connections > Sources](basic-connections.html#sources) and -[Connections > Targets](basic-connections.html#targets) applies here as well. +## Connection URI format -Following are some specifics for MQTT connections: +``` +tcp://hostname:1883 +``` + +Use `ssl://` for TLS-secured connections. -### Source format +## Source configuration -For an MQTT connection: +The common [source configuration](basic-connections.html#sources) applies, with these specifics: -* Source `"addresses"` are MQTT topics to subscribe to. Wildcards `+` and `#` are allowed. -* `"authorizationContext"` may _not_ contain placeholders `{%raw%}{{ header: }}{%endraw%}` as MQTT 3.1.1 - has no application headers. -* The required field `"qos"` sets the maximum Quality of Service to request when subscribing for messages. Its value - can be `0` for at-most-once delivery, `1` for at-least-once delivery and `2` for exactly-once delivery. - Support of any Quality of Service depends on the external MQTT broker; [AWS IoT][awsiot] for example does not +* `addresses` are MQTT topics to subscribe to (wildcards `+` and `#` allowed) +* `authorizationContext` may **not** contain `{%raw%}{{ header: }}{%endraw%}` placeholders (MQTT 3.1.1 has no application headers) +* `qos` (required) sets the maximum QoS to request: `0` (at-most-once), `1` (at-least-once), or `2` (exactly-once). + Support of any QoS level depends on the external MQTT broker; [AWS IoT][awsiot] for example does not acknowledge subscriptions with `qos=2`. ```json { - "addresses": [ - "", - "..." - ], - "authorizationContext": ["ditto:inbound-auth-subject", "..."], + "addresses": ["device/telemetry/#"], + "authorizationContext": ["ditto:inbound-auth-subject"], "qos": 2 } ``` -Note: This example assumes that there is a valid user named `ditto:inbound-auth-subject` in Ditto. -If you want to use a user for the basic auth (from the [HTTP API](connectivity-protocol-bindings-http.html)) use the prefix `nginx:`, e.g. `nginx:ditto`. -See [Basic Authentication](basic-auth.html#authorization-context-in-devops-commands) for more information. +### Source header mapping -#### Source header mapping +Although MQTT 3.1.1 has no protocol headers, Ditto extracts these from each consumed message: -MQTT 3.1.1 does not support headers in its protocol, however Ditto extracts the following headers from each consumed message: -* `mqtt.topic`: contains the MQTT topic on which a message was received -* `mqtt.qos`: contains the MQTT QoS value of a received message -* `mqtt.retain`: contains the MQTT retain flag of a received message +| Header | Description | +|--------|-------------| +| `mqtt.topic` | MQTT topic the message was received on | +| `mqtt.qos` | QoS value of the received message | +| `mqtt.retain` | Retain flag of the received message | -These headers may be used in a source header mapping, e.g.: ```json { "headerMapping": { @@ -78,35 +71,20 @@ These headers may be used in a source header mapping, e.g.: } ``` -#### Source acknowledgement handling +### Source acknowledgement handling -For MQTT 3.1.1 sources, when configuring -[acknowledgement requests](basic-connections.html#source-acknowledgement-requests), consumed messages from the MQTT 3.1.1 -broker are treated in the following way: +When you configure [acknowledgement requests](basic-connections.html#source-acknowledgement-requests): -For Ditto acknowledgements with successful [status](protocol-specification-acks.html#combined-status-code): -* Acknowledges the received MQTT 3.1.1 message +* **Successful** -- Ditto acknowledges the MQTT message +* **Failed with redelivery needed** -- depends on [reconnectForRedelivery](#reconnectforredelivery): either reconnects or withholds the ACK +* **Failed without redelivery** -- Ditto acknowledges the message (redelivery does not help) -For Ditto acknowledgements with mixed successful/failed [status](protocol-specification-acks.html#combined-status-code): -* If some of the aggregated [acknowledgements](basic-acknowledgements.html#acknowledgements-acks) require redelivery (e.g. based on a timeout): - * based on the [specificConfig](#specific-configuration) [reconnectForDelivery](#reconnectforredelivery) either - * closes and reconnects the MQTT connection in order to immediately receive unACKed QoS 1/2 messages again - * or simply doesn't acknowledge the received MQTT 3.1.1 message resulting in a redelivery of a QoS > 0 message by the MQTT broker -* If none of the aggregated [acknowledgements](basic-acknowledgements.html#acknowledgements-acks) require redelivery: - * acknowledges the received MQTT 3.1.1 message as redelivery does not make sense +To enable acknowledgement processing only for QoS 1/2 messages: -In order to enable acknowledgement processing only for MQTT messages received with QoS 1/2, the following configuration -has to be applied: ```json { - "addresses": [ - "", - "..." - ], - "authorizationContext": [ - "ditto:inbound-auth-subject", - "..." - ], + "addresses": ["device/#"], + "authorizationContext": ["ditto:inbound-auth-subject"], "qos": 1, "acknowledgementRequests": { "includes": [], @@ -115,21 +93,10 @@ has to be applied: } ``` -### Target format +## Target configuration -For an MQTT connection, the target address is the MQTT topic to publish events and messages to. -The target address may contain placeholders; see -[placeholders](basic-connections.html#placeholder-for-target-addresses) section for more information. - -Further, `"topics"` is a list of strings, each list entry representing a subscription of -[Ditto protocol topics](protocol-specification-topic.html). - -Outbound messages are published to the configured target address if one of the subjects in `"authorizationContext"` -has READ permission on the thing, which is associated with a message. - -The additional field `"qos"` sets the Quality of Service with which messages are published. -Its value can be `0` for at-most-once delivery, `1` for at-least-once delivery and `2` for exactly-once delivery. -Support of any Quality of Service depends on the external MQTT broker. +The common [target configuration](basic-connections.html#targets) applies. The target `address` is +the MQTT topic to publish to. The `qos` field sets the publishing QoS level: ```json { @@ -138,178 +105,107 @@ Support of any Quality of Service depends on the external MQTT broker. "_/_/things/twin/events", "_/_/things/live/messages" ], - "authorizationContext": ["ditto:outbound-auth-subject", "..."], + "authorizationContext": ["ditto:outbound-auth-subject"], "qos": 0 } ``` -#### Target header mapping +### Target header mapping -As MQTT 3.1.1 does not support headers in its protocol, a generic [header mapping](connectivity-header-mapping.html) is -not possible to configure here. +Generic header mapping is not available for MQTT 3.1.1 (no application headers). However, these +special headers are applied directly to the published MQTT message: -However, if one of the following headers are contained in the header mapping, they are directly applied to the -published MQTT message: -* `mqtt.topic`: overwrites the topic configured for the target -* `mqtt.qos`: overwrites the qos level configured for the target -* `mqtt.retain`: controls whether the MQTT retain flag is set on the published message +| Header | Effect | +|--------|--------| +| `mqtt.topic` | Overwrites the configured target topic | +| `mqtt.qos` | Overwrites the configured QoS level | +| `mqtt.retain` | Sets the MQTT retain flag | -#### Target acknowledgement handling +### Target acknowledgement handling -For MQTT 3.1.1 targets, when configuring -[automatically issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label), requested -acknowledgements are produced in the following way: +When you configure [issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label): -Once the MQTT 3.1.1 client signals that the message was acknowledged by the MQTT 3.1.1 broker, the following information -is mapped to the automatically created [acknowledgement](protocol-specification-acks.html#acknowledgement): -* Acknowledgement.status: - * will be `200`, if the message was successfully ACKed by the MQTT 3.1.1 broker or when the target has QoS 0 - * will be `503`, if the MQTT 3.1.1 broker ran into an error before an acknowledgement message was received -* Acknowledgement.value: - * will be missing, for status `200` - * will contain more information, in case that an error `status` was set +| Status | Condition | +|--------|-----------| +| `200` | Message ACKed by the broker (or target has QoS 0) | +| `503` | Broker error before acknowledgement was received | -### Specific Configuration +## Specific configuration options -The MQTT 3.1.1 binding offers additional [specific configurations](basic-connections.html#specific-configuration) -to apply for the used MQTT client. - -Overall example JSON of the MQTT `"specificConfig"`: ```json { - "id": "mqtt-example-connection-123", - "connectionType": "mqtt", - "connectionStatus": "open", - "failoverEnabled": true, - "uri": "tcp://test.mosquitto.org:1883", "specificConfig": { - "clientId": "my-awesome-mqtt-client-id", + "clientId": "my-mqtt-client-id", "reconnectForRedelivery": false, "cleanSession": false, "separatePublisherClient": false, - "publisherId": "my-awesome-mqtt-publisher-client-id", + "publisherId": "my-mqtt-publisher-id", "reconnectForRedeliveryDelay": "5s", + "keepAlive": "60s", "lastWillTopic": "my/last/will/topic", "lastWillQos": 1, "lastWillRetain": false, - "lastWillMessage": "my last will message" - }, - "sources": ["..."], - "targets": ["..."] + "lastWillMessage": "connection lost" + } } ``` -#### clientId - -Overwrites the default MQTT client id. - -Default: not set - the ID of the Ditto [connection](basic-connections.html) is used as MQTT client ID. - -#### reconnectForRedelivery - -Configures that the MQTT connection re-connects whenever a consumed message (via a connection source) with QoS 1 -("at least once") or 2 ("exactly once") -is processed but cannot be [acknowledged](#source-acknowledgement-handling) successfully. -That causes that the MQTT broker will re-publish the message once the connection reconnected. -If configured to `false`, the MQTT message is simply acknowledged (`PUBACK` or `PUBREC`, `PUBREL`). - -Default: `false` +### clientId -Handle with care: -* when set to `true`, incoming QoS 0 messages are lost during the reconnection phase -* when set to `true` and there is also an MQTT target configured to publish messages, - the messages to be published during the reconnection phase are lost - * to fix that, configure `"separatePublisherClient"` also to `true` in order to publish via another MQTT connection -* when set to `false`, MQTT messages with QoS 1 and 2 are redelivered based on the MQTT broker's strategy, - but may not be redelivered at all as the MQTT specification does not require unacknowledged messages to be redelivered - without reconnection of the client +Overwrites the default MQTT client ID. Default: the Ditto connection ID. -#### cleanSession +### reconnectForRedelivery -Configure the MQTT client's `cleanSession` flag. +When `true`, the MQTT connection reconnects whenever a consumed QoS 1 ("at least once") or 2 +("exactly once") message cannot be [acknowledged](#source-acknowledgement-handling) successfully. +The MQTT broker will then re-publish the message after reconnection. When `false`, the MQTT +message is simply acknowledged (`PUBACK` or `PUBREC`, `PUBREL`). Default: `false`. -Default: `false` +Handle with care: +* When `true`, incoming QoS 0 messages are lost during the reconnection phase +* When `true` and an MQTT target is also configured, outbound messages are lost during reconnection + -- to fix this, set `separatePublisherClient` to `true` to publish via a separate MQTT connection +* When `false`, MQTT messages with QoS 1 and 2 are redelivered based on the MQTT broker's strategy, + but may not be redelivered at all as the MQTT specification does not require unacknowledged + messages to be redelivered without reconnection of the client -#### separatePublisherClient +### cleanSession -Configures whether to create a separate physical client and connection to the MQTT broker for publishing messages, or not. -If configured to `true`, a single Ditto connection would open 2 MQTT connections/sessions: one for subscribing -and one for publishing. -If configured to `false`, the same MQTT connection/session is used both: for subscribing to messages, and for publishing -messages. +Sets the MQTT `cleanSession` flag. Default: `false`. -Default: `false` +### separatePublisherClient -#### publisherId +When `true`, Ditto opens two MQTT connections: one for subscribing and one for publishing. +Default: `false`. -Configures a specific MQTT client ID for the case that `"separatePublisherClient"` is enabled. +### publisherId -Default: -* if client ID is configured, `clientId` + `"p"` -* if no client ID is configured, `connectionId` + `"p"` +MQTT client ID for the publisher when `separatePublisherClient` is enabled. +Default: `clientId` + `"p"` (or `connectionId` + `"p"`). -#### reconnectForRedeliveryDelay +### reconnectForRedeliveryDelay -Configures how long to wait before reconnecting a consumer client for redelivery when `"reconnectForRedelivery"` -and `separatePublisherClient` are both enabled. The minimum value is `1s`. +Wait time before reconnecting for redelivery (minimum `1s`). Default: `2s`. -Default: `2s` +### keepAlive -#### keepAlive +Ping interval to check if the connection is still up. Default: `60s`. -Configures the keep alive time interval (in seconds) in which the client sends a ping to the broker -if no other MQTT packets are sent during this period of time. It is used to determine if the connection is still up. +### Last Will configuration -Default: `60s` [see here](https://hivemq.github.io/hivemq-mqtt-client/docs/mqtt-operations/connect/#keep-alive) +Configure a [Last Will](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028) +message to notify other clients of an ungraceful disconnect: -#### lastWillTopic - -Configures the topic which should be used on Last Will. This field is mandatory when Last Will should be enabled. - -#### lastWillQos - -Configures the QoS which should be used on Last Will: -- `0` = QoS 0 (“at most once”) -- `1` = QoS 1 (“at least once”) -- `2` = QoS 2 (“exactly once”) - -Default: `0` - -#### lastWillRetain - -Configures if clients which are newly subscribed to the topic chosen in [Last Will topic](#lastwilltopic) will -receive this message immediately after they subscribe. - -Default: `false` - -#### lastWillMessage - -Configures the message which should be published when the connection is disconnected ungracefully from the broker. -The message will be published as UTF8-encoded text on the topic chosen in [Last Will topic](#lastwilltopic). - -Default: empty string - -### Configure Last Will message - -To notify other clients when the connection is disconnected ungracefully the [Last Will feature](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028) -can be used. The message which will be published, is specified in the connection and stored -in the broker when it connects. The message contains a topic, retained message flag, QoS, and the text payload to be -published. These can be configured in the [Specific Configuration](#specific-configuration) of the connection. -The last will message is sent as text payload using UTF8 encoding. +| Property | Description | Default | +|----------|-------------|---------| +| `lastWillTopic` | Topic for the Last Will message (required to enable) | -- | +| `lastWillQos` | QoS level (`0`, `1`, or `2`) | `0` | +| `lastWillRetain` | Whether new subscribers receive the message immediately | `false` | +| `lastWillMessage` | UTF-8 text payload | empty string | {% include note.html content="This feature is enabled if the _last will topic_ is set." %} -## Establishing a connection to an MQTT 3.1.1 endpoint - -Ditto's [Connectivity service](architecture-services-connectivity.html) is responsible for creating new and managing -existing connections. - -This can be done dynamically at runtime without the need to restart any microservice using a -[Ditto DevOps command](installation-operating.html#devops-commands). - -Example: - -Connection configuration to create a new MQTT connection: +## Example connection JSON ```json { @@ -318,36 +214,24 @@ Connection configuration to create a new MQTT connection: "connectionStatus": "open", "failoverEnabled": true, "uri": "tcp://test.mosquitto.org:1883", - "sources": [ - { - "addresses": [ - "eclipse-ditto-sandbox/#" - ], - "authorizationContext": ["ditto:inbound-auth-subject"], - "qos": 0, - "filters": [] - } - ], - "targets": [ - { - "address": "eclipse-ditto-sandbox/{%raw%}{{ thing:id }}{%endraw%}", - "topics": [ - "_/_/things/twin/events" - ], - "authorizationContext": ["ditto:outbound-auth-subject"], - "qos": 0 - } - ] + "sources": [{ + "addresses": ["eclipse-ditto-sandbox/#"], + "authorizationContext": ["ditto:inbound-auth-subject"], + "qos": 0 + }], + "targets": [{ + "address": "eclipse-ditto-sandbox/{%raw%}{{ thing:id }}{%endraw%}", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], + "qos": 0 + }] } ``` -## Client-certificate authentication - -Ditto supports certificate-based authentication for MQTT connections. Consult -[Certificates for Transport Layer Security](connectivity-tls-certificates.html) -for how to set it up. +### Client-certificate authentication -Here is an example MQTT connection, which checks the broker certificate and authenticates by a client certificate. +Ditto supports certificate-based authentication for MQTT connections. See +[TLS certificates](connectivity-tls-certificates.html) for setup instructions. ```json { @@ -357,33 +241,30 @@ Here is an example MQTT connection, which checks the broker certificate and auth "failoverEnabled": true, "uri": "ssl://test.mosquitto.org:8884", "validateCertificates": true, - "ca": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", + "ca": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", "credentials": { "type": "client-cert", - "cert": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", + "cert": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", "key": "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----" }, - "sources": [ - { - "addresses": [ - "eclipse-ditto-sandbox/#" - ], - "authorizationContext": ["ditto:inbound-auth-subject"], - "qos": 0, - "filters": [] - } - ], - "targets": [ - { - "address": "eclipse-ditto-sandbox/{%raw%}{{ thing:id }}{%endraw%}", - "topics": [ - "_/_/things/twin/events" - ], - "authorizationContext": ["ditto:outbound-auth-subject"], - "qos": 0 - } - ] + "sources": [{ + "addresses": ["eclipse-ditto-sandbox/#"], + "authorizationContext": ["ditto:inbound-auth-subject"], + "qos": 0 + }], + "targets": [{ + "address": "eclipse-ditto-sandbox/{%raw%}{{ thing:id }}{%endraw%}", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], + "qos": 0 + }] } ``` +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Payload mapping](connectivity-mapping.html) -- transform message payloads +* [TLS certificates](connectivity-tls-certificates.html) -- secure connections with TLS + [awsiot]: https://docs.aws.amazon.com/iot/ diff --git a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-mqtt5.md b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-mqtt5.md index ed96e82ef0f..c3bc2ae1cdd 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-mqtt5.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-protocol-bindings-mqtt5.md @@ -5,113 +5,91 @@ tags: [protocol, connectivity] permalink: connectivity-protocol-bindings-mqtt5.html --- -Consume messages from MQTT 5 brokers via [sources](#source-format) and send messages to MQTT 5 brokers via -[targets](#target-format). +You use the MQTT 5 binding to connect Ditto with MQTT 5 brokers, gaining access to user-defined properties and enhanced message metadata. -## Content-type +{% include callout.html content="**TL;DR**: Configure an MQTT 5 connection with `connectionType: \"mqtt-5\"`. Source addresses are MQTT topics (wildcards `+` and `#` allowed). MQTT 5 supports user-defined properties for header mapping." type="primary" %} -When MQTT messages are sent in [Ditto Protocol](protocol-overview.html), -the payload should be `UTF-8` encoded strings. +## Overview -If messages, which are not in Ditto Protocol, should be processed, a [payload mapping](connectivity-mapping.html) must -be configured for the connection in order to transform the messages. +The MQTT 5 protocol binding lets you consume messages from MQTT 5 brokers via +[sources](#source-configuration) and publish messages via [targets](#target-configuration). -## MQTT 5 properties +MQTT payloads should be `UTF-8` encoded strings when sent in [Ditto Protocol](protocol-overview.html) +format. For other formats, configure a [payload mapping](connectivity-mapping.html). -Supported MQTT 5 properties, which are interpreted in a specific way are: +### MQTT 5 properties -* `9 (0x09) Correlation Data`: For correlating request messages and events. Twin events have the correlation IDs of - [Twin commands](protocol-twinlive.html#twin) that produced them. Stored in the ditto protocol header `correlation-id`. -* `8 (0x08) Response Topic`: The MQTT topic a requests response is expected in. - If a command sets the header `reply-to`, then its response is published at the topic equal to the header value. -* `3 (0x03) Content Type`: The UTF-8 encoded string representation of the payloads content MIME type. -* `2 (0x02) Message Expiry Interval`: Lifetime of the message in seconds. +Ditto interprets these MQTT 5 properties: -## Specific connection configuration +| Property | Description | +|----------|-------------| +| `9 (0x09) Correlation Data` | Correlation ID, stored in the `correlation-id` header | +| `8 (0x08) Response Topic` | MQTT topic for responses, mapped to the `reply-to` header | +| `3 (0x03) Content Type` | MIME type of the payload | +| `2 (0x02) Message Expiry Interval` | Message lifetime in seconds | -The common configuration for connections in [Connections > Sources](basic-connections.html#sources) and -[Connections > Targets](basic-connections.html#targets) applies here as well. +## Connection URI format -Following are some specifics for MQTT connections: +``` +tcp://hostname:1883 +``` + +Use `ssl://` for TLS-secured connections. -### Source format +## Source configuration -For an MQTT connection: +The common [source configuration](basic-connections.html#sources) applies, with these specifics: -* Source `"addresses"` are MQTT topics to subscribe to. Wildcards `+` and `#` are allowed. -* `"authorizationContext"` array that contains the authorization subjects in whose context -inbound messages are processed. These subjects may contain placeholders, see -[placeholders](basic-connections.html#placeholder-for-source-authorization-subjects) section for more information. -* The required field `"qos"` sets the maximum Quality of Service to request when subscribing for messages. Its value - can be `0` for at-most-once delivery, `1` for at-least-once delivery and `2` for exactly-once delivery. - Support of any Quality of Service depends on the external MQTT broker +* `addresses` are MQTT topics to subscribe to (wildcards `+` and `#` allowed) +* `authorizationContext` may contain `{%raw%}{{ header: }}{%endraw%}` placeholders (via user-defined properties) +* `qos` (required) sets the maximum QoS: `0` (at-most-once), `1` (at-least-once), or `2` (exactly-once) ```json { - "addresses": [ - "", - "..." - ], - "authorizationContext": ["ditto:inbound-auth-subject", "..."], + "addresses": ["device/telemetry/#"], + "authorizationContext": ["ditto:inbound-auth-subject"], "qos": 2 } ``` -#### Source header mapping +### Source header mapping -MQTT 5 supports so-called user defined properties, which are defined for every message type. -In addition, Ditto extracts the following headers from each consumed message: +MQTT 5 supports user-defined properties. Ditto also extracts these headers from each consumed message: -* `mqtt.topic`: contains the MQTT topic on which a message was received -* `mqtt.qos`: contains the MQTT QoS value of a received message -* `mqtt.retain`: contains the MQTT retain flag of a received message -* `mqtt.message-expiry-interval`: contains the MQTT 5 message expiry interval of a received message -* `correlation-id`: contains the MQTT 5 "correlation data" value -* `reply-to`: contains the MQTT 5 "response topic" value -* `content-type`: contains the MQTT 5 "content type" value +| Header | Description | +|--------|-------------| +| `mqtt.topic` | MQTT topic the message was received on | +| `mqtt.qos` | QoS value of the received message | +| `mqtt.retain` | Retain flag of the received message | +| `mqtt.message-expiry-interval` | Message expiry interval | +| `correlation-id` | MQTT 5 correlation data | +| `reply-to` | MQTT 5 response topic | +| `content-type` | MQTT 5 content type | -The [header mapping](connectivity-header-mapping.html) applies to the supported MQTT 5 specific headers as well -as to the user defined properties, e.g.: ```json { "headerMapping": { "topic": "{%raw%}{{ header:mqtt.topic }}{%endraw%}", - "the-qos": "{%raw%}{{ header:mqtt.qos }}{%endraw%}", "correlation-id": "{%raw%}{{ header:correlation-id }}{%endraw%}", "device-id": "{%raw%}{{ header:device-id-user-defined-property }}{%endraw%}" } } ``` -#### Source acknowledgement handling +### Source acknowledgement handling -For MQTT 5 sources, when configuring -[acknowledgement requests](basic-connections.html#source-acknowledgement-requests), consumed messages from the MQTT 5 -broker are treated in the following way: +When you configure [acknowledgement requests](basic-connections.html#source-acknowledgement-requests): -For Ditto acknowledgements with successful [status](protocol-specification-acks.html#combined-status-code): -* Acknowledges the received MQTT 5 message +* **Successful** -- Ditto acknowledges the MQTT 5 message +* **Failed with redelivery needed** -- depends on [reconnectForRedelivery](#reconnectforredelivery): either reconnects or withholds the ACK +* **Failed without redelivery** -- Ditto acknowledges the message -For Ditto acknowledgements with mixed successful/failed [status](protocol-specification-acks.html#combined-status-code): -* If some of the aggregated [acknowledgements](basic-acknowledgements.html#acknowledgements-acks) require redelivery (e.g. based on a timeout): - * based on the [specificConfig](#specific-configuration) [reconnectForDelivery](#reconnectforredelivery) either - * closes and reconnects the MQTT connection in order to immediately receive unACKed QoS 1/2 messages again - * or simply doesn't acknowledge the received MQTT 5 message resulting in a redelivery of a QoS > 0 message by the MQTT broker -* If none of the aggregated [acknowledgements](basic-acknowledgements.html#acknowledgements-acks) require redelivery: - * acknowledges the received MQTT 5 message as redelivery does not make sense +To enable acknowledgement processing only for QoS 1/2 messages: -In order to enable acknowledgement processing only for MQTT messages received with QoS 1/2, the following configuration -has to be applied: ```json { - "addresses": [ - "", - "..." - ], - "authorizationContext": [ - "ditto:inbound-auth-subject", - "..." - ], + "addresses": ["device/#"], + "authorizationContext": ["ditto:inbound-auth-subject"], "qos": 1, "acknowledgementRequests": { "includes": [], @@ -120,21 +98,10 @@ has to be applied: } ``` -### Target format +## Target configuration -For an MQTT connection, the target address is the MQTT topic to publish events and messages to. -The target address may contain placeholders; see -[placeholders](basic-connections.html#placeholder-for-target-addresses) section for more information. - -Further, `"topics"` is a list of strings, each list entry representing a subscription of -[Ditto protocol topics](protocol-specification-topic.html). - -Outbound messages are published to the configured target address if one of the subjects in `"authorizationContext"` -has READ permission on the thing, which is associated with a message. - -The additional field `"qos"` sets the Quality of Service with which messages are published. -Its value can be `0` for at-most-once delivery, `1` for at-least-once delivery and `2` for exactly-once delivery. -Support of any Quality of Service depends on the external MQTT broker. +The common [target configuration](basic-connections.html#targets) applies. The target `address` is +the MQTT topic to publish to. The `qos` field sets the publishing QoS level: ```json { @@ -143,255 +110,171 @@ Support of any Quality of Service depends on the external MQTT broker. "_/_/things/twin/events", "_/_/things/live/messages" ], - "authorizationContext": ["ditto:outbound-auth-subject", "..."], + "authorizationContext": ["ditto:outbound-auth-subject"], "qos": 0 } ``` -#### Target header mapping +### Target header mapping -MQTT 5 supports so-called user defined properties, which are defined for every message type. -The [header mapping](connectivity-header-mapping.html) applies to the supported MQTT 5 specific headers as well as to -the user defined properties. +MQTT 5 supports user-defined properties in the [header mapping](connectivity-header-mapping.html). +These special headers are applied directly to the published message: -The following headers have a special meaning in that the values are applied directly to the published message: -* `mqtt.topic`: overwrites the topic configured for the target -* `mqtt.qos`: overwrites the qos level configured in the target -* `mqtt.retain`: controls whether the MQTT retain flag is set on the published message -* `mqtt.message-expiry-interval`: sets MQTT 5 message expiry interval of the published message +| Header | Effect | +|--------|--------| +| `mqtt.topic` | Overwrites the configured target topic | +| `mqtt.qos` | Overwrites the configured QoS level | +| `mqtt.retain` | Sets the MQTT retain flag | +| `mqtt.message-expiry-interval` | Sets the message expiry interval | -#### Target acknowledgement handling +### Target acknowledgement handling -For MQTT 5 targets, when configuring -[automatically issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label), requested -acknowledgements are produced in the following way: +When you configure [issued acknowledgement labels](basic-connections.html#target-issued-acknowledgement-label): -Once the MQTT 5 client signals that the message was acknowledged by the MQTT 5 broker, the following information -is mapped to the automatically created [acknowledgement](protocol-specification-acks.html#acknowledgement): -* Acknowledgement.status: - * will be `200`, if the message was successfully ACKed by the MQTT 5 broker - * will be `503`, if the MQTT 5 broker ran into an error before an acknowledgement message was received -* Acknowledgement.value: - * will be missing, for status `200` - * will contain more information, in case that an error `status` was set +| Status | Condition | +|--------|-----------| +| `200` | Message ACKed by the broker | +| `503` | Broker error before acknowledgement was received | -### Specific Configuration +## Specific configuration options -The MQTT 5 binding offers additional [specific configurations](basic-connections.html#specific-configuration) -to apply for the used MQTT client. - -Overall example JSON of the MQTT `"specificConfig"`: ```json { - "id": "mqtt-example-connection-123", - "connectionType": "mqtt-5", - "connectionStatus": "open", - "failoverEnabled": true, - "uri": "tcp://test.mosquitto.org:1883", "specificConfig": { - "clientId": "my-awesome-mqtt-client-id", + "clientId": "my-mqtt5-client-id", "reconnectForRedelivery": false, "cleanSession": false, "separatePublisherClient": false, - "publisherId": "my-awesome-mqtt-publisher-client-id", + "publisherId": "my-mqtt5-publisher-id", "reconnectForRedeliveryDelay": "5s", + "keepAlive": "60s", "lastWillTopic": "my/last/will/topic", "lastWillQos": 1, "lastWillRetain": false, - "lastWillMessage": "my last will message" - }, - "sources": ["..."], - "targets": ["..."] + "lastWillMessage": "connection lost" + } } ``` -#### clientId - -Overwrites the default MQTT client id. - -Default: not set - the ID of the Ditto [connection](basic-connections.html) is used as MQTT client ID. - -If the connection's `clientCount` is 2 or more, -the ID of each connectivity service instance is appended to the client ID to prevent clients from having the same -ID. Otherwise the broker will disconnect the already-connected client every time another client with the same -ID connects. - -#### reconnectForRedelivery - -Configures that the MQTT connection re-connects whenever a consumed message (via a connection source) with QoS 1 -("at least once") 2 ("exactly once") is processed but cannot be [acknowledged](#source-acknowledgement-handling) successfully.
    -That causes that the MQTT broker will re-publish the message once the connection reconnected. -If configured to `false`, the MQTT message is simply acknowledged (`PUBACK` or `PUBREC`, `PUBREL`). +### clientId -Default: `false` +Overwrites the default MQTT client ID. Default: the Ditto connection ID. -Handle with care: -* when set to `true`, incoming QoS 0 messages are lost during the reconnection phase -* when set to `true` and there is also an MQTT target configured to publish messages, - the messages to be published during the reconnection phase are lost - * to fix that, configure `"separatePublisherClient"` also to `true` in order to publish via another MQTT connection -* when set to `false`, MQTT messages with QoS 1 and 2 are redelivered based on the MQTT broker's strategy, - but may not be redelivered at all as the MQTT specification does not require unacknowledged messages to be redelivered - without reconnection of the client +If the connection's `clientCount` is 2 or more, the ID of each connectivity service instance is +appended to the client ID to prevent clients from having the same ID. Otherwise the broker will +disconnect the already-connected client every time another client with the same ID connects. -#### cleanSession +### reconnectForRedelivery -Configure the MQTT client's `cleanStart` flag. (The flag is called `cleanStart` but the option is `cleanSession` to -be consistent with MQTT 3 specific config.) +When `true`, the MQTT connection reconnects whenever a consumed QoS 1 ("at least once") or 2 +("exactly once") message cannot be [acknowledged](#source-acknowledgement-handling) successfully. +The MQTT broker will then re-publish the message after reconnection. When `false`, the MQTT +message is simply acknowledged (`PUBACK` or `PUBREC`, `PUBREL`). Default: `false`. -Default: `false` +Handle with care: +* When `true`, incoming QoS 0 messages are lost during the reconnection phase +* When `true` and an MQTT target is also configured, outbound messages are lost during reconnection + -- to fix this, set `separatePublisherClient` to `true` to publish via a separate MQTT connection +* When `false`, MQTT messages with QoS 1 and 2 are redelivered based on the MQTT broker's strategy, + but may not be redelivered at all as the MQTT specification does not require unacknowledged + messages to be redelivered without reconnection of the client -#### separatePublisherClient +### cleanSession -Configures whether to create a separate physical client and connection to the MQTT broker for publishing messages, or not. -If configured to `true`, a single Ditto connection would open 2 MQTT connections/sessions: one for subscribing -and one for publishing. -If configured to `false`, the same MQTT connection/session is used both: for subscribing to messages, and for publishing -messages. +Sets the MQTT 5 `cleanStart` flag. (The flag is called `cleanStart` in MQTT 5 but the option is +named `cleanSession` for consistency with MQTT 3.1.1.) Default: `false`. -Default: `false` +### separatePublisherClient -#### publisherId +When `true`, Ditto opens two MQTT connections: one for subscribing and one for publishing. +Default: `false`. -Configures a specific MQTT client ID for the case that `"separatePublisherClient"` is enabled. +### publisherId -Default: -* if client ID is configured, `clientId` + `"p"` -* if no client ID is configured, `connectionId` + `"p"` +MQTT client ID for the publisher when `separatePublisherClient` is enabled. +Default: `clientId` + `"p"` (or `connectionId` + `"p"`). -#### reconnectForRedeliveryDelay +### reconnectForRedeliveryDelay -Configures how long to wait before reconnecting a consumer client for redelivery when `"reconnectForRedelivery"` -and `separatePublisherClient` are both enabled. The minimum value is `1s`. +Wait time before reconnecting for redelivery (minimum `1s`). Default: `2s`. -Default: `2s` +### keepAlive -#### keepAlive +Ping interval to check if the connection is still up. Default: `60s`. -Configures the keep alive time interval (in seconds) in which the client sends a ping to the broker -if no other MQTT packets are sent during this period of time. It is used to determine if the connection is still up. +### Last Will configuration -Default: `60s` [see here](https://hivemq.github.io/hivemq-mqtt-client/docs/mqtt-operations/connect/#keep-alive) +Configure a [Last Will](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901033) +message to notify other clients of an ungraceful disconnect: -#### lastWillTopic - -Configures the topic which should be used on Last Will. This field is mandatory when Last Will should be enabled. - -#### lastWillQos - -Configures the QoS which should be used on Last Will: -- `0` = QoS 0 (“at most once”) -- `1` = QoS 1 (“at least once”) -- `2` = QoS 2 (“exactly once”) - -Default: `0` - -#### lastWillRetain - -Configures if clients which are newly subscribed to the topic chosen in [Last Will topic](#lastwilltopic) will -receive this message immediately after they subscribe. - -Default: `false` - -#### lastWillMessage - -Configures the message which should be published when the connection is disconnected ungracefully from the broker. -The message will be published as UTF8-encoded text on the topic chosen in [Last Will topic](#lastwilltopic). - -Default: empty string - -### Configure Last Will message - -To notify other clients when the connection is disconnected ungracefully the [Last Will feature](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901033) -can be used. The message which will be published, is specified in the connection and stored in the broker when it -connects. The message contains a topic, retained message flag, QoS, and the text payload to be published. These can be -configured in the [Specific Configuration](#specific-configuration) of the connection. +| Property | Description | Default | +|----------|-------------|---------| +| `lastWillTopic` | Topic for the Last Will message (required to enable) | -- | +| `lastWillQos` | QoS level (`0`, `1`, or `2`) | `0` | +| `lastWillRetain` | Whether new subscribers receive the message immediately | `false` | +| `lastWillMessage` | UTF-8 text payload | empty string | {% include note.html content="This feature is enabled if the _last will topic_ is set." %} -## Establishing a connection to an MQTT 5 endpoint - -Ditto's [Connectivity service](architecture-services-connectivity.html) is responsible for creating new and managing -existing connections. - -This can be done dynamically at runtime without the need to restart any microservice using a -[Ditto DevOps command](installation-operating.html#devops-commands). - -Example: - -Connection configuration to create a new MQTT connection: +## Example connection JSON ```json { - "id": "mqtt-example-connection-12", + "id": "mqtt5-example-connection-12", "connectionType": "mqtt-5", "connectionStatus": "open", "failoverEnabled": true, "uri": "tcp://test.mosquitto.org:1883", - "sources": [ - { - "addresses": [ - "eclipse-ditto-sandbox/#" - ], - "authorizationContext": ["ditto:inbound-auth-subject"], - "qos": 0, - "filters": [] - } - ], - "targets": [ - { - "address": "eclipse-ditto-sandbox/{%raw%}{{ thing:id }}{%endraw%}", - "topics": [ - "_/_/things/twin/events" - ], - "authorizationContext": ["ditto:outbound-auth-subject"], - "qos": 0 - } - ] + "sources": [{ + "addresses": ["eclipse-ditto-sandbox/#"], + "authorizationContext": ["ditto:inbound-auth-subject"], + "qos": 0 + }], + "targets": [{ + "address": "eclipse-ditto-sandbox/{%raw%}{{ thing:id }}{%endraw%}", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], + "qos": 0 + }] } ``` -## Client-certificate authentication - -Ditto supports certificate-based authentication for MQTT connections. Consult -[Certificates for Transport Layer Security](connectivity-tls-certificates.html) -for how to set it up. +### Client-certificate authentication -Here is an example MQTT connection, which checks the broker certificate and authenticates by a client certificate. +Ditto supports certificate-based authentication for MQTT 5 connections. See +[TLS certificates](connectivity-tls-certificates.html) for setup instructions. ```json { - "id": "mqtt-example-connection-124", + "id": "mqtt5-example-connection-124", "connectionType": "mqtt-5", "connectionStatus": "open", "failoverEnabled": true, "uri": "ssl://test.mosquitto.org:8884", "validateCertificates": true, - "ca": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", + "ca": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", "credentials": { "type": "client-cert", - "cert": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", + "cert": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", "key": "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----" }, - "sources": [ - { - "addresses": [ - "eclipse-ditto-sandbox/#" - ], - "authorizationContext": ["ditto:inbound-auth-subject"], - "qos": 0, - "filters": [] - } - ], - "targets": [ - { - "address": "eclipse-ditto-sandbox/{%raw%}{{ thing:id }}{%endraw%}", - "topics": [ - "_/_/things/twin/events" - ], - "authorizationContext": ["ditto:outbound-auth-subject"], - "qos": 0 - } - ] + "sources": [{ + "addresses": ["eclipse-ditto-sandbox/#"], + "authorizationContext": ["ditto:inbound-auth-subject"], + "qos": 0 + }], + "targets": [{ + "address": "eclipse-ditto-sandbox/{%raw%}{{ thing:id }}{%endraw%}", + "topics": ["_/_/things/twin/events"], + "authorizationContext": ["ditto:outbound-auth-subject"], + "qos": 0 + }] } ``` + +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [Payload mapping](connectivity-mapping.html) -- transform message payloads +* [Header mapping](connectivity-header-mapping.html) -- map external headers +* [TLS certificates](connectivity-tls-certificates.html) -- secure connections with TLS diff --git a/documentation/src/main/resources/pages/ditto/connectivity-ssh-tunneling.md b/documentation/src/main/resources/pages/ditto/connectivity-ssh-tunneling.md index e25f1ab10a7..ebdc4d7fc41 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-ssh-tunneling.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-ssh-tunneling.md @@ -1,99 +1,117 @@ --- -title: SSH tunneling +title: SSH Tunneling keywords: ssh, tunnel, tunneling, port forwarding tags: [connectivity] permalink: connectivity-ssh-tunneling.html --- -## SSH tunneling +You use SSH tunneling to reach endpoints that are not directly accessible, by routing the connection through an SSH server. -A managed connection supports establishing an SSH tunnel -(see section TCP/IP Port Forwarding of the -[Secure Shell (SSH) Connection Protocol, RFC4254](https://tools.ietf.org/html/rfc4254#section-7)) which -is then used to connect to the actual target endpoint. -This is useful when the target endpoint is not directly accessible but only via SSH. For this purpose the connection -configuration must specify the `sshTunnel` section, which contains the necessary -information to establish a local SSH port forwarding. The tunneling supports password and public key authentication and -host validation using public key fingerprints. If the tunnel is enabled the connection will establish an SSH -tunnel and afterwards use this tunnel to connect to the actual endpoint. +{% include callout.html content="**TL;DR**: Add an `sshTunnel` section to your connection configuration with the SSH server URI and credentials. Ditto establishes local port forwarding and connects to the target endpoint through the tunnel." type="primary" %} -The example below establishes an SSH tunnel via `ssh-host:2222` to the remote endpoint -`tcp://mqtt.eclipseprojects.io:1883`, using plain authentication and enabled host validation: +## Overview + +A managed connection can establish an SSH tunnel using +[TCP/IP port forwarding (RFC 4254)](https://tools.ietf.org/html/rfc4254#section-7) to connect to +endpoints that are only reachable via an SSH server. Ditto first opens the SSH tunnel, then connects +to the actual endpoint through it. + +The tunneling supports: + +* Password authentication +* Public key authentication +* Host validation using public key fingerprints + +{% include note.html content="SSH tunneling can impact transmission performance compared to a direct connection." %} + +## Configuration + +Add an `sshTunnel` section to your connection configuration: ```json { - "name": "tunneled-connection", - "connectionType": "mqtt", - "uri": "tcp://mqtt.eclipseprojects.io:1883", - "sources": [{ ... }], - "sshTunnel": { - "enabled": true, - "uri": "ssh://ssh-host:2222", - "credentials": { - "type": "plain", - "username": "username", - "password": "password" - }, - "validateHost": true, - "knownHosts": ["MD5:e0:3a:34:1c:68:ed:c6:bc:7c:ca:a8:67:c7:45:2b:19"] - } + "name": "tunneled-connection", + "connectionType": "mqtt", + "uri": "tcp://mqtt.eclipseprojects.io:1883", + "sources": [{ "..." : "..." }], + "sshTunnel": { + "enabled": true, + "uri": "ssh://ssh-host:2222", + "credentials": { + "type": "plain", + "username": "username", + "password": "password" + }, + "validateHost": true, + "knownHosts": ["MD5:e0:3a:34:1c:68:ed:c6:bc:7c:ca:a8:67:c7:45:2b:19"] + } } ``` -{% include note.html content="When using SSH tunneling, keep in mind that it can have an impact on the transmission -performance of your connection compared to transmission performance of a direct connection." %} +This example tunnels through `ssh-host:2222` to reach `tcp://mqtt.eclipseprojects.io:1883`. + +### Password authentication + +Set `credentials.type` to `plain` and provide `username` and `password` as shown above. ### Public key authentication -An SSH tunnel can also be authenticated using public key authentication. The credentials provided in the SSH tunnel -configuration must then be of the type `public-key`: +Set `credentials.type` to `public-key` and provide the key pair: + ```json -... -"credentials": { +{ + "credentials": { "type": "public-key", "username": "username", "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9.....\n-----END PUBLIC KEY-----", "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhki....\n-----END PRIVATE KEY-----" + } } -... ``` -The public key must be provided as PEM-encoded RSA key in `X.509` format. -The private key must be provided as PEM-encoded RSA key in unencrypted `PKCS8` format as specified by -[RFC-7468](https://tools.ietf.org/html/rfc7468). +* The public key must be PEM-encoded RSA in `X.509` format +* The private key must be PEM-encoded RSA in unencrypted `PKCS8` format per + [RFC-7468](https://tools.ietf.org/html/rfc7468) + +To convert a PKCS1 key to PKCS8: -The following command can be used to convert a standard OpenSSL key in PKCS1 format to the PKCS8 format accepted by -Ditto: ``` openssl pkcs8 -topk8 -nocrypt -in client-private.pem.key -out client-private.pem.pk8 ``` -{% include note.html content="Ditto does not make any sanity check regarding the provided credentials or the -provided SSH server, e.g. if the server uses outdated ciphers or insecure keys. So make sure you configure only trusted -servers that meet your security requirements. As an additional security measure, the user associated with the given -credentials should only have assigned the least required privileges (i.e. allow only local port forwarding but no +{% include note.html content="Ditto does not make any sanity check regarding the provided credentials or the +provided SSH server, e.g. if the server uses outdated ciphers or insecure keys. So make sure you configure only trusted +servers that meet your security requirements. As an additional security measure, the user associated with the given +credentials should only have assigned the least required privileges (i.e. allow only local port forwarding but no shell access)." %} -### SSH host validation +### Host validation -{% include note.html content="It is highly recommended enabling host validation for productive systems, it should +{% include note.html content="It is highly recommended enabling host validation for productive systems, it should only be disabled for testing purposes." %} -The accepted fingerprints can be provided in the format the standard command line tool `ssh-keygen` produces. +Provide fingerprints in the format produced by `ssh-keygen`. Ditto supports these hash algorithms: +`MD5`, `SHA1`, `SHA224`, `SHA256`, `SHA384`, `SHA512`. -Example: -``` -MD5:e0:3a:34:1c:68:ed:c6:bc:7c:ca:a8:67:c7:45:2b:19 -``` -The fingerprints are prefixed with an alias of the hash algorithm that was used to calculate the fingerprint. Ditto -supports the following hash algorithms for public key fingerprints: `MD5`, `SHA1`, `SHA224`, `SHA256`, `SHA384` and `SHA512`. +Generate a fingerprint from a public key file: -Assuming the file `id_rsa.pub` contains the public key the following command produces a valid fingerprint that -can be used in the SSH tunnel configuration: ``` ssh-keygen -lf id_rsa.pub -E md5 ``` -Or in case the public key is given in PKCS8 format: + +For PKCS8 format keys: + ``` ssh-keygen -lf id_rsa.pub.pkcs8 -m PKCS8 -E md5 ``` + +Example fingerprint: + +``` +MD5:e0:3a:34:1c:68:ed:c6:bc:7c:ca:a8:67:c7:45:2b:19 +``` + +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [TLS certificates](connectivity-tls-certificates.html) -- secure connections with TLS diff --git a/documentation/src/main/resources/pages/ditto/connectivity-tls-certificates.md b/documentation/src/main/resources/pages/ditto/connectivity-tls-certificates.md index cefb54aaef4..c5eae55a2ba 100644 --- a/documentation/src/main/resources/pages/ditto/connectivity-tls-certificates.md +++ b/documentation/src/main/resources/pages/ditto/connectivity-tls-certificates.md @@ -1,5 +1,5 @@ --- -title: Certificates for Transport Layer Security +title: TLS Certificates keywords: security, TLS tags: [connectivity] permalink: connectivity-tls-certificates.html @@ -14,65 +14,58 @@ permalink: connectivity-tls-certificates.html [rfc5280]: https://tools.ietf.org/html/rfc5280 [rfc7468]: https://tools.ietf.org/html/rfc7468 -## Verify server certificate +You use TLS certificates to secure connections between Ditto and external message brokers, verifying server identity and optionally authenticating Ditto as a client. -_Server-certificate verification is available for +{% include callout.html content="**TL;DR**: Set `validateCertificates: true` and provide a `ca` certificate to verify server identity. Add `credentials` with type `client-cert` to authenticate Ditto with a client certificate." type="primary" %} + +## Overview + +Ditto supports two certificate-based security features: + +1. **Server certificate verification** -- confirms the identity of the external endpoint +2. **Client certificate authentication** -- authenticates Ditto at the external endpoint + +Both features are available for [AMQP 0.9.1](connectivity-protocol-bindings-amqp091.html), [AMQP 1.0](connectivity-protocol-bindings-amqp10.html), [MQTT 3.1.1](connectivity-protocol-bindings-mqtt.html), [MQTT 5](connectivity-protocol-bindings-mqtt5.html), [HTTP 1.1](connectivity-protocol-bindings-http.html), and -[Kafka 2.x](connectivity-protocol-bindings-kafka2.html) connections._ +[Kafka 2.x](connectivity-protocol-bindings-kafka2.html) connections. -### Connection configuration +## How it works -Verifying the server identity mitigates the risk of man-in-the-middle attacks. -To have Ditto check the identity of external message brokers, -choose a secure transport protocol and set the flag `validateCertificates` to `true` in your -[connections](basic-connections.html). +### Server certificate verification + +Verifying the server identity mitigates man-in-the-middle attacks. To enable it, use a secure +transport protocol and set `validateCertificates` to `true`: ```json { - "uri": "://:/", + "uri": "://:/", "validateCertificates": true, "ca": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----" } ``` -- `uri`: Choose a secure transport protocol such as `amqps` or `ssl`. -- `validateCertificates`: Must be `true`. -- `ca`: A string of trusted certificates as [PEM][pem]-encoded [DER][der]. Concatenate multiple certificates as - strings to trust all of them. Omit to trust popular certificate authorities. - -### Server identity check - -When Ditto opens a secure connection, the external message broker provides a server certificate. -The server identity is verified directly or indirectly. - -- **Direct identity verification**: - The exact server certificate is listed as a trusted certificate in the connection configuration `ca`. - Establishing a TLS session proves that the external message broker possesses the private key matching the server - certificate. +| Field | Description | +|-------|-------------| +| `uri` | Use a secure protocol such as `amqps`, `ssl`, or `https` | +| `validateCertificates` | Must be `true` | +| `ca` | Trusted certificates as [PEM][pem]-encoded [DER][der]. Concatenate multiple certificates to trust all of them. Omit to trust public CAs. | -- **Indirect identity verification via a trusted party**: - The server certificate is signed by a trusted party, whose certificate is in the connection configuration `ca`, and - the host-component of the connection URI is listed in the server certificate. +Ditto verifies the server identity in one of three ways: - If the host-component is a DNS name, then it should be listed as the common name (CN) - or a subject alternative name (SAN) of type DNS. +**Direct verification** -- the exact server certificate is in the `ca` field. TLS proves the +server possesses the matching private key. - If the host-component is an IPv4 or IPv6 address, then it should be listed as a subject alternative name (SAN) - of type IP. Any IP addresses in the common name (CN) are ignored per [RFC-5280][rfc5280]. +**Indirect verification via trusted party** -- the server certificate is signed by a CA whose +certificate is in the `ca` field, and the connection URI hostname matches the server certificate +(as CN or SAN). IPv4/IPv6 addresses must be listed as a SAN of type IP per [RFC-5280][rfc5280]. +Revocation of individual certificates is not supported. - Revocation of individual certificates is *not supported*. For each certificate in the `ca` field, you extend trust to - _all_ certificates signed by it. - -- **Indirect identity verification via public certificate authorities**: - The server certificate is signed by a generally accepted certificate authority, the host-component - of the connection URI is listed in the server certificate, and the `ca` field is omitted in the connection - configuration. - - Ditto will try its best to exclude revoked certificates via [OCSP][ocsp]. +**Indirect verification via public CAs** -- the `ca` field is omitted, and the server certificate +is signed by a generally accepted CA. Ditto attempts to exclude revoked certificates via [OCSP][ocsp]. {% include note.html content="Certificates signed by public CAs get compromised on a daily basis. It is more secure to upload your message broker's certificate directly even if it was signed @@ -80,23 +73,19 @@ The server identity is verified directly or indirectly. To minimize unavailability due to certificate expiry, upload both: the current broker certificate and the next certificate as a concatenated string." %} -## Authenticate by client certificate +### Client certificate authentication _Client-certificate authentication is available for -[MQTT connections](connectivity-protocol-bindings-mqtt.html), -[HTTP connections](connectivity-protocol-bindings-http.html), +[MQTT](connectivity-protocol-bindings-mqtt.html), +[HTTP](connectivity-protocol-bindings-http.html), [AMQP 1.0](connectivity-protocol-bindings-amqp10.html), and -[Kafka 2.x](connectivity-protocol-bindings-kafka2.html) -connections only._ - -### Connection configuration +[Kafka 2.x](connectivity-protocol-bindings-kafka2.html) connections._ -Configure a client certificate for Ditto in the `credentials` field of the connection configuration to authenticate -Ditto at your message broker. +Configure a client certificate to authenticate Ditto at your message broker: ```json { - "uri": "://:/", + "uri": "://:/", "credentials": { "type": "client-cert", "cert": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----", @@ -105,19 +94,23 @@ Ditto at your message broker. } ``` -- `uri`: Choose a secure transport protocol such as `ssl`. +| Field | Description | +|-------|-------------| +| `credentials/type` | Must be `client-cert` | +| `credentials/cert` | Client certificate as [PEM][pem]-encoded [DER][der] | +| `credentials/key` | Client private key as PEM-encoded [PKCS8][pkcs8] per [RFC-7468][rfc7468]. The PEM preamble must be `-----BEGIN PRIVATE KEY-----`. | -- `credentials/type`: Must be `client-cert`. +### Converting PKCS1 keys to PKCS8 -- `credentials/cert`: The client certificate as [PEM][pem]-encoded [DER][der]. - -- `credentials/key`: The client private key for Ditto as PEM-encoded [PKCS8][pkcs8] specified by [RFC-7468][rfc7468]; -the PEM preamble must be `-----BEGIN PRIVATE KEY-----`. - -As of September 2018, [OpenSSL][openssl] and [AWS IoT][awsiot] generate PKCS1-coded private keys by default, which -have the PEM preamble `-----BEGIN RSA PRIVATE KEY-----`. Ditto will reject these keys. The command below converts a -PKCS1 key file `client-private.pem.key` into a PKCS8 key file `client-private.pem.pk8` accepted by Ditto. +[OpenSSL][openssl] and [AWS IoT][awsiot] generate PKCS1 keys by default +(`-----BEGIN RSA PRIVATE KEY-----`), which Ditto rejects. Convert with: ``` openssl pkcs8 -topk8 -nocrypt -in client-private.pem.key -out client-private.pem.pk8 ``` + +## Further reading + +* [Connections overview](basic-connections.html) -- connection model and configuration +* [SSH tunneling](connectivity-ssh-tunneling.html) -- tunnel connections through SSH +* [HMAC signing](connectivity-hmac-signing.html) -- HMAC-based authentication diff --git a/documentation/src/main/resources/pages/ditto/feedback.md b/documentation/src/main/resources/pages/ditto/feedback.md index f075ba5bc6f..4c0521dd3f4 100644 --- a/documentation/src/main/resources/pages/ditto/feedback.md +++ b/documentation/src/main/resources/pages/ditto/feedback.md @@ -6,9 +6,28 @@ permalink: feedback.html topnav: topnav --- -You have following possibilities in order to get support or give feedback: +You can get support, ask questions, and contribute feedback through several community channels. -* Join the chat at [https://gitter.im/eclipse/ditto](https://gitter.im/eclipse/ditto?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) for questions -* start a [GitHub discussion](https://github.com/eclipse-ditto/ditto/discussions) for questions and general discussions -* As your question on [StackOverflow](https://stackoverflow.com/questions/tagged/eclipse-ditto) -* create a [GitHub issue](https://github.com/eclipse-ditto/ditto/issues) for found bugs / feature requests +{% include callout.html content="**TL;DR**: Ask questions on GitHub Discussions or StackOverflow. Report bugs and request features via GitHub Issues. Chat in real time on Gitter." type="primary" %} + +## Overview + +The Eclipse Ditto community is active across multiple platforms. Choose the channel that best fits your need. + +## Community channels + +### Chat + +Join the conversation at [Gitter](https://gitter.im/eclipse/ditto?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) for real-time discussions and quick questions. + +### Discussions + +Start a [GitHub Discussion](https://github.com/eclipse-ditto/ditto/discussions) for questions, ideas, and general conversations about Ditto. + +### StackOverflow + +Ask your question on [StackOverflow](https://stackoverflow.com/questions/tagged/eclipse-ditto) using the `eclipse-ditto` tag to reach a broader developer audience. + +### Bug reports and feature requests + +Create a [GitHub Issue](https://github.com/eclipse-ditto/ditto/issues) to report bugs or request new features. diff --git a/documentation/src/main/resources/pages/ditto/glossary.md b/documentation/src/main/resources/pages/ditto/glossary.md index 9179cb63ada..7080839a55a 100644 --- a/documentation/src/main/resources/pages/ditto/glossary.md +++ b/documentation/src/main/resources/pages/ditto/glossary.md @@ -6,6 +6,10 @@ permalink: glossary.html toc: false --- +This glossary defines key terms used throughout the Eclipse Ditto documentation. + +{% include callout.html content="**TL;DR**: Quick reference for IoT, Ditto-specific, and architectural terms you will encounter in the documentation." type="primary" %} + ## Common terms IoT @@ -14,6 +18,9 @@ IoT IIoT : {{site.data.glossary.iiot}} +JWT +: {{site.data.glossary.jwt}} + CQRS : {{site.data.glossary.cqrs}} @@ -23,8 +30,7 @@ RQL SSE : {{site.data.glossary.sse}} - -## Ditto specific terms +## Ditto-specific terms Asset : {{site.data.glossary.asset}} diff --git a/documentation/src/main/resources/pages/ditto/httpapi-concepts.md b/documentation/src/main/resources/pages/ditto/httpapi-concepts.md index a55828cce3f..9c709a776ce 100644 --- a/documentation/src/main/resources/pages/ditto/httpapi-concepts.md +++ b/documentation/src/main/resources/pages/ditto/httpapi-concepts.md @@ -1,118 +1,128 @@ --- -title: HTTP API concepts -keywords: http, api, concepts, partial, conditional, optimistic locking, ETag, If-Match, If-None-Match +title: HTTP API Concepts +keywords: http, api, concepts, partial, conditional, optimistic locking, ETag, If-Match, If-None-Match tags: [http] permalink: httpapi-concepts.html --- -Ditto's [HTTP API](http-api-doc.html) follows some concepts which are documented on this page. +The Ditto HTTP API follows REST conventions and maps the JSON structure of digital twins directly to API endpoints, giving you fine-grained access to every piece of twin data. + +{% include callout.html content="**TL;DR**: The HTTP API auto-generates endpoints from your Thing's JSON structure, supports partial reads/writes, merge updates via `PATCH`, and conditional requests with ETags for optimistic locking." type="primary" %} + +## Overview + +The HTTP API entry point is: -The entry point into the HTTP API is: ``` http://localhost:8080/api/ ``` -## API versioning +Explore the full API interactively in the [HTTP API documentation](http-api-doc.html). -Ditto's HTTP API is versioned in the URL: `/api/`. Currently, Ditto only provides API version `2`. -API version 1 was deprecated and deleted as of Ditto version 2.0.0 +## API versioning -The API version is a promise that no HTTP resources (the static ones defined by Ditto itself) are modified in an -incompatible/breaking way. As the HTTP resources reflect the JSON structure of the `Thing` entity, that also applies -for this entity. +Ditto versions its HTTP API in the URL path: `/api/`. Currently, only API version `2` is available (version 1 was removed in Ditto 2.0.0). -In API 2 the `Thing` structure contains a [Policy](basic-policy.html) where the authorization information is -managed. +The version guarantee means that no existing HTTP resources or JSON structures change in breaking ways within the same API version. In API 2, each [Thing](basic-thing.html) references a [Policy](basic-policy.html) for authorization. ## Endpoints -In the HTTP API, some endpoints are static and can be seen as the "schema" of Ditto. They are in sync with the JSON -representation of the model classes, e.g. [Thing](basic-thing.html#model-specification) for the layout of the `/things` -endpoint and [Policy](basic-policy.html) for the layout of the `/policies` endpoint, and `/checkPermissions` for verifying -access rights of entities on specific resources based on defined policies. - -### API version 2 +The HTTP API has two types of endpoints: **static** endpoints that mirror the Thing/Policy/Connection data model, and **dynamic** endpoints that Ditto generates automatically from your Thing's JSON structure. -In API version 2, a `Thing` contains a `policyId`, which points to a `Policy` managed as another entity. -Its API endpoint is `/policies`. +### Static endpoints -#### `/things` in API 2 +#### `/things` endpoints -The base endpoint for accessing and working with `Things`.
    -A `Thing` in API 2 has the following JSON structure: +A `Thing` in API 2 has this JSON structure: ```json { "thingId": "{thingId}", "policyId": "{policyId}", "definition": "{definition}", - "attributes": { - }, - "features": { + "attributes": {}, + "features": {} +} +``` + +This maps to these API endpoints: + +* `/things/{thingId}` -- the complete Thing +* `/things/{thingId}/policyId` -- the Policy ID +* `/things/{thingId}/definition` -- the Thing definition +* `/things/{thingId}/attributes` -- all attributes +* `/things/{thingId}/features` -- all features + +#### `/policies` endpoints + +A `Policy` has this JSON structure: + +```json +{ + "policyId": "{policyId}", + "entries": { + "{entryLabel}": { + "subjects": { "{subjectId}": {} }, + "resources": { "{resource}": {} } + } } } ``` -This maps to the following HTTP API endpoints: +This maps to: + +* `/policies/{policyId}` -- the complete `Policy` +* `/policies/{policyId}/entries` -- all `Policy` entries +* `/policies/{policyId}/entries/{entryLabel}` -- a single entry +* `/policies/{policyId}/entries/{entryLabel}/subjects` -- subjects of an entry +* `/policies/{policyId}/entries/{entryLabel}/resources` -- resources of an entry -* `/things/{thingId}`: accessing a complete specific thing -* `/things/{thingId}/policyId`: accessing the policy ID of the specific thing -* `/things/{thingId}/definition`: accessing the definition of the specific thing -* `/things/{thingId}/attributes`: accessing the attributes of the specific thing -* `/things/{thingId}/features`: accessing the features of the specific thing +#### `/connections` endpoints -#### `/things` in API 2 - dynamic part +* `/connections` -- list all connections or create a new one +* `/connections/{connectionId}` -- a specific connection +* `/connections/{connectionId}/command` -- send a command +* `/connections/{connectionId}/status` -- retrieve status +* `/connections/{connectionId}/metrics` -- retrieve metrics +* `/connections/{connectionId}/logs` -- retrieve logs -Additionally, to that "static part" of the HTTP API which is defined by Ditto, the API is dynamically enhanced by the -JSON structure of the Thing.
    +### Dynamic endpoints {% include note.html content="This automatically turns each small aspect of a **digital twin** into an API endpoint." %} -For example for a `Thing` with following content: +Ditto generates additional endpoints based on your `Thing`'s actual JSON content. For example, given this `Thing`: ```json { "thingId": "{thingId}", "policyId": "{policyId}", - "definition": "{definition}", "attributes": { "manufacturer": "ACME corp", - "complex": { - "some": false, - "serialNo": 4711 - } + "complex": { "some": false, "serialNo": 4711 } }, "features": { "lamp": { - "properties": { - "on": false, - "color": "blue" - } + "properties": { "on": false, "color": "blue" } } } } ``` -The following additional API endpoints are automatically available: +These additional endpoints become available automatically: -* `/things/{thingId}/attributes/manufacturer`: accessing the attribute `manufacturer` of the specific thing -* `/things/{thingId}/attributes/complex`: accessing the attribute `complex` of the specific thing -* `/things/{thingId}/attributes/complex/some`: accessing the attribute `complex/some` of the specific thing -* `/things/{thingId}/attributes/complex/serialNo`: accessing the attribute `complex/serialNo` of the specific thing -* `/things/{thingId}/features/lamp`: accessing the feature `lamp` of the specific thing -* `/things/{thingId}/features/lamp/properties`: accessing all properties of the feature `lamp` of the specific thing -* `/things/{thingId}/features/lamp/properties/on`: accessing the `on` property of the feature `lamp` of the specific - thing -* `/things/{thingId}/features/lamp/properties/color`: accessing the `color` properties of the feature `lamp` of the - specific thing +* `/things/{thingId}/attributes/manufacturer` +* `/things/{thingId}/attributes/complex` +* `/things/{thingId}/attributes/complex/some` +* `/things/{thingId}/attributes/complex/serialNo` +* `/things/{thingId}/features/lamp` +* `/things/{thingId}/features/lamp/properties` +* `/things/{thingId}/features/lamp/properties/on` +* `/things/{thingId}/features/lamp/properties/color` -#### `/things` in API 2 - Migrate Thing Definitions +### Migrate Thing definitions -The endpoint `/things/{thingId}/migrateDefinition` allows migrating the thing definition with a new model, as well as optionally migrating attributes and features. +Use `POST /things/{thingId}/migrateDefinition` to migrate a Thing to a new model version, optionally updating attributes and features. -HTTP Method: `POST /things/{thingId}/migrateDefinition` - -Request Example: ```json { "thingDefinitionUrl": "https://models.example.com/thing-definition-1.0.0.tm.jsonld", @@ -125,10 +135,7 @@ Request Example: "thermostat": { "properties": { "status": { - "temperature": { - "value": 23.5, - "unit": "DEGREE_CELSIUS" - } + "temperature": { "value": 23.5, "unit": "DEGREE_CELSIUS" } } } } @@ -141,70 +148,14 @@ Request Example: } ``` -String values in `migrationPayload` may use the [thing-json placeholder](basic-placeholders.html#thing-json-placeholder) -(brace or legacy). When the value is exactly one such placeholder (no pipeline), the resolved value preserves its JSON type. Pipelines and multiple placeholders yield a string. Resolution uses the existing Thing. Missing paths cause the request to fail. - -#### `/policies` in API 2 - -The base endpoint for accessing and working with `Policies`.
    -A `Policy` in API 2 has the following JSON structure: - -```json -{ - "policyId": "{policyId}", - "entries": { - "{entryLabel-1}": { - "subjects": { - "{subjectId1}": { - } - }, - "resources": { - "{resource1}": { - } - } - } - } -} -``` - -This maps to the following HTTP API endpoints: - -* `/policies/{policyId}`: accessing complete `Policy` -* `/policies/{policyId}/entries`: accessing the `Policy` entries -* `/policies/{policyId}/entries/{entryLabel-1}`: accessing a single `Policy` entry with the label `{entryLabel-1}` -* `/policies/{policyId}/entries/{entryLabel-1}/subjects`: accessing the subjects of a single `Policy` entry with the - label `{entryLabel-1}` -* `/policies/{policyId}/entries/{entryLabel-1}/resources`: accessing the resources of a single `Policy` entry with the - label `{entryLabel-1}` - -#### `/connections` in API 2 - -The base endpoint for accessing and working with `Connections`. - -It has the following HTTP API endpoints: - -* `/connections`: accessing all connections or create a new connection -* `/connections/{connectionId}`: accessing a specific `Connection` entry -* `/connections/{connectionId}/command`: send a command to a specific Connection -* `/connections/{connectionId}/status`: retrieve status of a specific Connection -* `/connections/{connectionId}/metrics`: retrieve metrics of a specific Connection -* `/connections/{connectionId}/logs`: retrieve logs of a specific Connection +String values in `migrationPayload` may use the [thing-json placeholder](basic-placeholders.html#thing-json-placeholder) (brace or legacy). When the value is exactly one such placeholder (no pipeline), the resolved value preserves its JSON type. Pipelines and multiple placeholders yield a string. Resolution uses the existing Thing. Missing paths cause the request to fail. ## Partial updates -As a benefit of the above-mentioned mechanism that an API is automatically available based on the JSON structure, the -"partial update" pattern can be applied when modifying data. - -The benefit of this is a reduction in payload to be transferred. Further, it is beneficial because other parts of the -`Thing` are not overwritten with a potentially outdated value - only the actually changed data part can be modified. +Because every JSON element has its own endpoint, you can update individual values without touching the rest of the `Thing`. This reduces payload size and prevents accidentally overwriting data with stale values. -So instead of modifying a complete `Thing` only a specific part is affected. - -Given, the `on` property of `lamp` should be changed to `true`. - -Instead of -
    -`PUT .../things/{thingId}` with the complete payload: +For example, to change the `on` property of `lamp` to `true`, instead of replacing the entire +Thing with `PUT .../things/{thingId}` and the full JSON body: ```json { @@ -213,23 +164,19 @@ Instead of "definition": "{definition}", "attributes": { "manufacturer": "ACME corp", - "complex": { - "some": false, - "serialNo": 4711 - } + "complex": { "some": false, "serialNo": 4711 } }, "features": { "lamp": { - "properties": { - "on": true, - "color": "blue" - } + "properties": { "on": true, "color": "blue" } } } } ``` -we can use a smarter request
    `PUT .../things/{thingId}/features/lamp/properties/on` with a minimal payload: +You target only the changed value: + +`PUT .../things/{thingId}/features/lamp/properties/on` ```json true @@ -237,30 +184,21 @@ true ## Partial requests -Similar to the partial updates from above, the HTTP API can also be used to retrieve a single value instead of a -complete `Thing`. +You can also read individual values instead of the full `Thing`: -Again, the benefit is a reduction in response payload and that the caller can directly use the returned data value -(for example expect it to be a `boolean` and treat it accordingly). - -For example, we can request
    `GET .../things/{thingId}/features/lamp/properties/on` and get as response: +`GET .../things/{thingId}/features/lamp/properties/on` ```json true ``` -### With field selector - -A further mechanism in the API for partial requests is using a so-called field selector. This is useful when the JSON -structure of the `Thing` or other entity should be kept intact, but not all information is relevant. - -The field selector is passed as an HTTP query parameter `fields` and contains a comma separated list of fields to include -in the response. +### Field selectors -It is possible to use the wildcard operator '*' as feature ID and retrieve a property of multiple features. -For details see the example [below](#field-selector-with-wildcard). +Use the `fields` query parameter to retrieve specific fields while preserving the JSON structure. +Pass a comma-separated list of field paths. You can also use `*` as a feature ID wildcard to +retrieve a property across multiple features. -Given, you have the following Thing: +Given this Thing: ```json { @@ -269,24 +207,14 @@ Given, you have the following Thing: "definition": "{definition}", "attributes": { "manufacturer": "ACME corp", - "complex": { - "some": false, - "serialNo": 4711, - "misc": "foo" - } + "complex": { "some": false, "serialNo": 4711, "misc": "foo" } }, "features": { "lamp": { - "properties": { - "on": true, - "color": "blue" - } + "properties": { "on": true, "color": "blue" } }, "infrared-lamp": { - "properties": { - "on": false, - "color": "red" - } + "properties": { "on": false, "color": "red" } } } } @@ -294,27 +222,18 @@ Given, you have the following Thing: #### Field selector examples -The following `GET` request examples with field selectors show how you can retrieve only the parts of a thing which you -are interested in: - -`GET .../things/{thingId}?fields=attributes`
    -Response: +`GET .../things/{thingId}?fields=attributes` ```json { "attributes": { "manufacturer": "ACME corp", - "complex": { - "some": false, - "serialNo": 4711, - "misc": "foo" - } + "complex": { "some": false, "serialNo": 4711, "misc": "foo" } } } ``` -`GET .../things/{thingId}?fields=attributes/manufacturer`
    -Response: +`GET .../things/{thingId}?fields=attributes/manufacturer` ```json { @@ -324,8 +243,7 @@ Response: } ``` -`GET .../things/{thingId}?fields=attributes/complex/serialNo`
    -Response: +`GET .../things/{thingId}?fields=attributes/complex/serialNo` ```json { @@ -337,158 +255,81 @@ Response: } ``` -`GET .../things/{thingId}?fields=attributes/complex/some,attributes/complex/serialNo`
    -Response: +`GET .../things/{thingId}?fields=attributes/complex/some,attributes/complex/serialNo` ```json { "attributes": { - "complex": { - "some": false, - "serialNo": 4711 - } + "complex": { "some": false, "serialNo": 4711 } } } ``` -`GET .../things/{thingId}?fields=attributes/complex(some,serialNo)`
    -Response: +The same result using parentheses to group fields under the same parent: + +`GET .../things/{thingId}?fields=attributes/complex(some,serialNo)` ```json { "attributes": { - "complex": { - "some": false, - "serialNo": 4711 - } + "complex": { "some": false, "serialNo": 4711 } } } ``` -`GET .../things/{thingId}?fields=attributes/complex/misc,features/lamp/properties/on`
    -Response: +Selecting fields from different branches of the JSON: + +`GET .../things/{thingId}?fields=attributes/complex/misc,features/lamp/properties/on` ```json { "attributes": { - "complex": { - "misc": "foo" - } + "complex": { "misc": "foo" } }, "features": { "lamp": { - "properties": { - "on": true - } + "properties": { "on": true } } } } ``` -##### Field selector with wildcard +#### Wildcard field selectors -`GET .../things/{thingId}?fields=features/*/properties/on`
    -Response: +Use `*` as a feature ID wildcard to retrieve a property across all features: + +`GET .../things/{thingId}?fields=features/*/properties/on` ```json { "features": { - "lamp": { - "properties": { - "on": true - } - }, - "infrared-lamp": { - "properties": { - "on": false - } - } + "lamp": { "properties": { "on": true } }, + "infrared-lamp": { "properties": { "on": false } } } } ``` ## Merge updates -Merge updates can be used to update multiple parts of a Thing _in a single request_ e.g. multiple properties of -different features or a feature property and an attribute value. Merge updates are applied by using the HTTP -`PATCH` method with the payload in [JSON merge patch (RFC-7396)](https://tools.ietf.org/html/rfc7396) format. The -content-type of the request must be set to `application/merge-patch+json`. - -[RFC-7396](https://tools.ietf.org/html/rfc7396) specifies how a set of modifications is applied to an existing JSON -document: +Use `PATCH` with [JSON Merge Patch (RFC 7396)](https://tools.ietf.org/html/rfc7396) to update multiple parts of a Thing in a single request. Set the content type to `application/merge-patch+json`. -``` -A JSON merge patch document describes changes to be made to a target JSON document using a syntax that closely -mimics the document being modified. Recipients of a merge patch document determine the exact set of changes being -requested by comparing the content of the provided patch against the current content of the target document. -If the provided merge patch contains members that do not appear within the target, those members are added. If the -target does contain the member, the value is replaced. Null values in the merge patch are given special meaning to -indicate the removal of existing values in the target. -``` +The merge patch rules are: +* New members are added +* Existing members are replaced with the new value +* Members set to `null` are removed {% include note.html content="Please note the special meaning of `null` values. When using `PATCH` a `null` value is interpreted as delete in contrast to `PUT` requests where `null` values have no special meaning. " %} -Like `PUT` requests, `PATCH` requests can be applied at any level of the JSON structure of a thing, e.g. patching a -complete thing at root level or patching a single property value at property level. +You can apply `PATCH` at any level of the JSON structure. -### Removing fields in a merge update with a regex +### Removing fields with a regex {% include note.html content="This is an addition to the JSON merge patch (RFC-7396), enhancing using `null` values for deleting certain parts of JSON objects specified with a regular expression before applying new fields to it." %} -The merge patch functionality in Ditto solves a common problem to the JSON merge patch (RFC-7396): whenever a JSON object -shall be patched, all the old json fields are merged with all the new json fields, unless the exact field names are -specified in the patch with `"": null`. -This would however require to know all existing fields upfront, which for a merge patch can not be assumed. - -The solution is a little enhancement to Ditto's merge patch functionality: The ability to delete arbitrary parts from -JSON objects using a regular expression **before** applying all other patch values. - -The syntax for this function is rather specific (so that no "normally" occurring JSON keys match the same syntax): -```json -{ - "{%raw%}{{ ~.*~ }}{%endraw%}": null -} -``` - -The recommended regex delimiter to use is the `~` character, additionally supported delimiters at this point are: -* `~` -* `/` (discouraged due to the special meaning of `/` for HTTP paths) - -A discouraged and thus deprecated way to define the regex (using `/` as delimiter) is: -```json -{ - "{%raw%}{{ /.*/ }}{%endraw%}": null -} -``` - -When such a `{%raw%}{{ ~~ }}{%endraw%}` with the value `null` is detected in the merge patch, the content between the 2 delimiters is -interpreted as regular expression to apply for finding keys to delete from the target object. -As a result, using `"{%raw%}{{ ~.*~ }}{%endraw%}": null` would delete all the values inside a JSON object before applying the new -values provided in the patch. - -Example: -Assuming that inside a JSON object every month some aggregated data is stored with the year and month: -```json -{ - "thingId": "{thingId}", - "policyId": "{policyId}", - "features": { - "aggregated-history": { - "properties": { - "2022-11": 42.3, - "2022-12": 54.3, - "2023-01": 80.2, - "2023-02": 99.9 - } - } - } -} -``` +Ditto extends RFC 7396 with regex-based field removal. Use the syntax `{%raw%}{{ ~~ }}{%endraw%}` with a `null` value to delete matching keys before applying the rest of the patch: -Then the data from "last year" could be purged with the following patch, while adding a new value to the existing ones -of this year: ```json { "features": { @@ -502,71 +343,36 @@ of this year: } ``` -The resulting Thing JSON after applying the patch would then look like: -```json -{ - "thingId": "{thingId}", - "policyId": "{policyId}", - "features": { - "aggregated-history": { - "properties": { - "2023-01": 80.2, - "2023-02": 99.9, - "2023-03": 105.21 - } - } - } -} -``` +This removes all keys matching `2022-.*` from `properties`, then adds the new value. The recommended delimiter is `~`. The `/` delimiter is also supported but discouraged due to its special meaning in HTTP paths. -### Permissions required for merge update +### Merge update permissions -To successfully execute merge update the authorized subject needs to have *WRITE* permission on *all* resources -affected by the provided JSON merge patch. If the permission is missing for one of the affected resources the whole -merge patch is *rejected*, i.e. the merge update is executed as a whole or not at all. +You need `WRITE` permission on **all** resources affected by the merge patch. If permission is missing for any affected resource, the entire patch is rejected. ### Merge update example -Given an existing thing with the JSON structure: +Given an existing Thing with this JSON: ```json { "thingId": "{thingId}", "policyId": "{policyId}", "attributes": { - "location": { - "longitude": 47.682170, - "latitude": 9.386372 - }, + "location": { "longitude": 47.682170, "latitude": 9.386372 }, "serialNo": "0000000" }, "features": { "temperature": { - "properties": { - "value": 25.43, - "unit": "°C" - } + "properties": { "value": 25.43, "unit": "°C" } }, "pressure": { - "properties": { - "value": 1013.25, - "unit": "hPa" - } + "properties": { "value": 1013.25, "unit": "hPa" } } } } ``` -Assuming a single request should: - -* add the `manufacturer` attribute -* update the existing `serialNo` attribute to the value of `23091861` -* remove the existing `location` attribute -* set the existing property `value` of feature `temperature` to the value of `26.89` -* remove the existing property `unit` of feature `pressure` -* add a new feature `humidity` - -This can be achieved using a `PATCH .../things/{thingId}` with the request payload of +A single `PATCH .../things/{thingId}` can add, update, and remove multiple fields: ```json { @@ -577,26 +383,19 @@ This can be achieved using a `PATCH .../things/{thingId}` with the request paylo }, "features": { "temperature": { - "properties": { - "value": 26.89 - } + "properties": { "value": 26.89 } }, "pressure": { - "properties": { - "unit": null - } + "properties": { "unit": null } }, "humidity": { - "properties": { - "value": 55, - "unit": "%" - } + "properties": { "value": 55, "unit": "%" } } } } ``` -The resulting JSON representation of the updated thing after applying the `PATCH` is: +The resulting Thing after applying the patch: ```json { @@ -608,120 +407,59 @@ The resulting JSON representation of the updated thing after applying the `PATCH }, "features": { "temperature": { - "properties": { - "value": 26.89, - "unit": "°C" - } + "properties": { "value": 26.89, "unit": "°C" } }, "pressure": { - "properties": { - "value": 1015 - } + "properties": { "value": 1013.25 } }, "humidity": { - "properties": { - "value": 55, - "unit": "%" - } + "properties": { "value": 55, "unit": "%" } } } } ``` -## Conditional Requests +This patch removes `location`, adds `manufacturer`, updates `serialNo`, changes the temperature +value, removes the pressure unit, and adds a new `humidity` feature -- all in one request. + +## Conditional requests -The HTTP API for `Things` and `Policies` partially supports `Conditional Requests` as defined -in [RFC-7232](https://tools.ietf.org/html/rfc7232). +The HTTP API for `Things` and `Policies` partially supports [Conditional Requests (RFC 7232)](https://tools.ietf.org/html/rfc7232). ### ETag -A successful response on a `thing` or `policy` resource provides an `ETag` header. - -* For read responses, it contains the current entity-tag of the resource. -* For write responses, it contains the entity-tag after successful write. - -The `ETag` has a different format for top-level resources and sub-resources. - -* Top-level resources (e.g. `.../things/{thingId}`): The entity-tag contains the revision of the entity which is - addressed by the resource in the format `"rev:"`, e.g. `"rev:2"`. -* Sub-resources (e.g. `.../things/{thingId}/features/{featureId}`): The entity-tag contains a hash of the current value - of the addressed sub-resource in the format `"hash:"`, e.g. - `"hash:87192253740"`. Note that this format may change in the future. - -### Conditional Headers - -The following request headers can be used to issue a conditional request: - -* `If-Match`: - * Read or write the resource only - * if the current entity-tag matches at least one of the entity-tags provided in this header - * or if the header is `*` and the entity exists - * The response will be: - * in case of a match, the same response as if the header wouldn't have been specified - * in case of no match, status `412 (Precondition Failed)` with an error response containing detail information - and the current entity-tag of the resource as `ETag` header -* `If-None-Match`: - * Read or write the resource only - * if the current entity-tag does not match any one of the entity-tags provided in this header - * or if the header is `*` and the entity does not exist - * The response will be: - * in case of no match, the same response as if the header wouldn't have been specified - * in case of a match: - * for write requests, status `412 (Precondition Failed)` with an error response containing detail - information and the current entity-tag of the resource as `ETag` header - * for read requests, status `304 (Not Modified)` without response body, with the current entity-tag of the - resource as `ETag` header -* `if-equal`: - * The `if-equal` header can take the values `'update'` (which is the default if omitted), `'skip'` or `'skip-minimizing-merge'` - * Modify/Update the resource only - * in case `if-equal: 'update'` is defined always updates - even if the entity is equal before the update. - * in case `if-equal: 'skip'` is defined, the entity will not be updated if it is equal before the update. - In this case a 'Precondition Failed' 412 status is returned. - * in case `if-equal: 'skip-minimizing-merge'` is defined, the entity will not be updated if it is equal before the update. - In this case a 'Precondition Failed' 412 status is returned. - Additionally, [Merge commands](protocol-specification-things-merge.html) will be minimized to only the fields - which actually changed, compared to the current state of the entity. - * this reduces the part of a merge/patch command to the actually changed elements, removing non-changed elements - * this reduces e.g. required storage in the MongoDB by a lot, if redundant data is sent often - * this also reduces the event payload (e.g. emitted to subscribers) to only the actually changed parts of the thing - -Note that the Ditto HTTP API always provides a `strong` entity-tag in the `ETag` header, thus you will never receive a -`weak` entity-tag (see [RFC-7232 Section 2.1](https://tools.ietf.org/html/rfc7232#section-2.1)). If you convert this -strong entity-tag to a weak entity-tag and use it in a Conditional Header, Ditto will handle it according to RFC-7232. -However, we discourage the usage of weak entity-tags, because in the context of Ditto they only add unnecessary -complexity. - -In addition to the `ETag` header Ditto supports conditional requests with a `condition` header. For further information -see [Conditional Requests](basic-conditional-requests.html) +Successful responses include an `ETag` header: -### Exempted fields +* **Top-level resources** (e.g., `.../things/{thingId}`): `"rev:"` (e.g., `"rev:2"`) +* **Sub-resources** (e.g., `.../things/{thingId}/features/{featureId}`): `"hash:"` -Assuming you have a thing with an associated policy. When querying the thing with +### Conditional headers -``` -GET .../things/{thingId}?fields=_policy -``` +| Header | Behavior | +|--------|----------| +| `If-Match` | Proceed only if the current entity-tag matches. Use `*` to require the entity exists. Returns `412` on mismatch. | +| `If-None-Match` | Proceed only if the current entity-tag does NOT match. Use `*` to require the entity does NOT exist. Returns `412` for writes or `304` for reads on mismatch. | +| `if-equal` | Controls update behavior: `update` (default) always updates; `skip` returns `412` if the entity is unchanged; `skip-minimizing-merge` does the same but also reduces merge commands to only changed fields. | -you will get the thing containing its revision and associated policy. +The `skip-minimizing-merge` option for `if-equal` is particularly useful for reducing MongoDB storage and event payload when redundant data is sent frequently. -If you now modify the associated policy, the revision of the thing will not change! This could lead to an inconsistent -state if the thing is getting refetched by using the `If-None-Match` header, because this would return -a `304 Not Modified`, even if the policy has changed. +Ditto always provides strong entity-tags. See [Conditional Requests](basic-conditional-requests.html) for using the `condition` header with RQL expressions. -To tackle this, Ditto has the following list of exempted fields which automatically bypass the precondition header -check: +### Exempted fields -* `_policy` +When querying a Thing with: -### Examples +``` +GET .../things/{thingId}?fields=_policy +``` -The following examples show several scenarios on a top-level (Thing) resource. Nevertheless, these scenarios can also be -applied on any sub-resource in the same way. +you get the Thing with its associated policy. If you modify the associated policy, the Thing's +revision does not change, so `If-None-Match` checks based on the Thing's ETag would not detect +the policy change. Ditto exempts `_policy` from precondition checks to prevent inconsistencies. -#### Create: Write only if the resource does not exist +### Examples -The following example request shows, how you can make sure that a `PUT` request does not overwrite existing data, i.e. -how you can enforce that the Thing can only be created by the request. +#### Create only if the Thing does not exist ``` PUT .../things/{thingId} @@ -738,16 +476,10 @@ If-None-Match: * } ``` -You will get one of the following responses: +* `201 Created` -- the Thing was created successfully +* `412 Precondition Failed` -- a Thing with this ID already exists -* `201 (Created)` in case the creation was successful, i.e. the Thing did not yet exist. -* `412 (Precondition Failed)` in case the creation failed, i.e. a Thing with the exactly same `{thingId}` already - exists. - -#### Update: Write only if the resource already exists - -The following example request shows how you can make sure that a `PUT` request does not create the resource, i.e. how -you can enforce that the Thing can only be updated by the request, but you do not generate a duplicate by mistake. +#### Update only if the Thing exists ``` PUT .../things/{thingId} @@ -763,14 +495,12 @@ If-Match: * } ``` -You will get one of the following responses: - -* `204 (No Content)` in case the update was successful, i.e. the Thing already existed. -* `412 (Precondition Failed)` in case the update failed, i.e. the Thing does not yet exist. +* `204 No Content` -- the Thing was updated successfully +* `412 Precondition Failed` -- the Thing does not exist -#### Optimistic Locking +#### Optimistic locking -First, `GET` the Thing in order to retrieve both: the current data and the entity-tag: +First, retrieve the Thing and its ETag: `GET .../things/{thingId}`: @@ -790,12 +520,7 @@ Response: } ``` -Assume that you have detected the typo in the manufacturer attribute ("ACME crop") and want to fix this with a top-level -Thing PUT. You want to make sure, that no one else has modified the Thing in the meantime, because otherwise his changes -would be lost. (You could also achieve this with a PUT on the concrete attribute, but for this example we assume that -you want to use a top-level Thing PUT.) - -`PUT` the Thing with the changed data and the entity-tag from the preceding `GET` response in the `If-Match` header. +Then update with the ETag in `If-Match` to fix the typo without overwriting concurrent changes: ``` PUT .../things/{thingId} @@ -811,8 +536,12 @@ If-Match: "rev:2" } ``` -You will get one of the following responses: +* `204 No Content` -- no one else changed the Thing since your `GET` +* `412 Precondition Failed` -- someone else modified the Thing in the meantime + +## Further reading -* `204 (No Content)` in case the update was successful, i.e. no one else has changed the Thing in the meantime. -* `412 (Precondition Failed)` in case the update was not successful, i.e. the Thing has been changed by someone else in - the meantime. +* [HTTP API documentation](http-api-doc.html) -- interactive API reference +* [HTTP API search](httpapi-search.html) -- querying Things with RQL +* [HTTP API messages](httpapi-messages.html) -- sending messages to/from Things +* [Conditional requests](basic-conditional-requests.html) -- RQL-based conditions diff --git a/documentation/src/main/resources/pages/ditto/httpapi-messages.md b/documentation/src/main/resources/pages/ditto/httpapi-messages.md index c3a390127e7..6326197f472 100644 --- a/documentation/src/main/resources/pages/ditto/httpapi-messages.md +++ b/documentation/src/main/resources/pages/ditto/httpapi-messages.md @@ -5,34 +5,35 @@ tags: [http] permalink: httpapi-messages.html --- -The HTTP API allows sending Messages **to** and **from** Things and its Features. -To dive into the basic concepts of the Messages functionality, please have a look -at the [Messages page](basic-messages.html). +You send messages to and from Things and Features through the HTTP API, enabling command-and-control communication with devices. + +{% include callout.html content="**TL;DR**: Send a `POST` to a Thing's or Feature's `inbox` to send it a message, or to its `outbox` to send a message from it. Use the `timeout` parameter to control how long Ditto waits for a response." type="primary" %} {% include tip.html content="Check out the [WebSocket Messages API](protocol-specification-things-messages.html) if you also need to *receive* or *reply* to Messages." %} -This page gives you a quick hands-on introduction to the HTTP Messages API. To learn -about the parameters, constraints, possible responses, etc. move over to the -[HTTP API Documentation](http-api-doc.html#/Messages). +## Overview + +The HTTP Messages API lets you send messages **to** and **from** Things and their Features. For the underlying concepts, see the [Messages](basic-messages.html) page. For full parameter details and response codes, see the [HTTP API Documentation](http-api-doc.html#/Messages). + +## How it works -## Using the HTTP Messages API +Messages flow through two paths: -The following parts contain examples on how to send to and from Things and Features. -For the examples we will use some kind of smart coffee machine with the id *smartcoffee*. +* **Inbox** -- send a message **to** a Thing or Feature (the device receives it) +* **Outbox** -- send a message **from** a Thing or Feature (simulating device-originated messages) + +When you send a message to a Thing's inbox, Ditto routes it to the device. If the device responds, you receive the response as the HTTP response body. If the device does not respond in time, you get a `408 Request Timeout`. + +## Examples {% include note.html content="Don't forget to replace the Authorization header and the host when trying out the examples. Also make sure the Thing, you are sending Messages to, is existing." %} -The examples use `cURL` for the HTTP requests. You can of course choose -whatever tool you prefer to work with. - -### Sending a Message to a Thing +### Send a message to a Thing -A message is always sent **to** the **inbox** of the receiving entity. -Let us view a simple Message that asks our Thing *smartcoffee* how it is feeling -today: +Send a message with subject `ask` to the Thing `org.eclipse.ditto:smartcoffee`: ```bash curl --request POST \ @@ -42,46 +43,27 @@ curl --request POST \ --data 'Hey, how are you?' ``` -Notice we are sending the Message to the *inbox* of our Thing *org.eclipse.ditto:smartcoffee*. -The subject of the Message is *'ask'* and contains plain text as content. -Short after, we would receive a response from smartcoffee: +If the device responds, you receive its reply as the HTTP response body. -```text -I do not know, since i am only a coffee machine. -``` - -But what would happen if smartcoffee was offline or for some other reason -could not respond to our Message? This would cause the HTTP-Request to end -in a timeout. This is especially annoying when sending a Message for which -we don't expect a response. +### Control the timeout -This is why Ditto introduced the `timeout` query parameter to the requests. -With it, you can specify how long Ditto should wait for a response -before closing the HTTP request with a timeout: +If you do not need a response (fire-and-forget), set `timeout=0` to get an immediate `202 Accepted`: ```bash curl --request POST \ - --url http://localhost:8080/api/2/things/org.eclipse.ditto:smartcoffee/inbox/messages/ask.question?timeout=0 \ + --url http://localhost:8080/api/2/things/org.eclipse.ditto:smartcoffee/inbox/messages/ask?timeout=0 \ --header 'content-type: text/plain' \ --header 'Authorization: Basic ZGl0dG86ZGl0dG8=' \ --data 'Hey, how are you?' ``` -You will instantly receive a `202 Accepted` from Ditto instead of the -`408 Request Timeout` response. - -With the `timeout` query parameter you can also choose a different timeout than the -default one provided by Ditto. - {% include tip.html content="Use the `timeout` query parameter to specify what timeout you expect for your Messages. A timeout of *zero* will instantly return a response, whilst other positive values change how long Ditto will wait for an answer before responding to you." %} -### Sending a Message to a Feature +### Send a message to a Feature -Sending a Message to a Feature works just about the same way as sending it to a Thing. -The only difference is the URL to which you will need to send the Message. See -how we can ask the *water-tank* Feature of our Thing *smartcoffee* to heat up: +Target a specific Feature by including its ID in the URL path: ```bash curl --request POST \ @@ -91,11 +73,9 @@ curl --request POST \ --data 'heatUp' ``` -### Sending a Message from a Thing or Feature +### Send a message from a Thing -Sending a Message **from** a Thing or Feature works just as you would expect. -Simply replace the *inbox* path of the URI with *outbox*. Think again of our -Thing smartcoffee, which needs to inform about something: +Replace `inbox` with `outbox` to send a message **from** the Thing: ```bash curl --request POST \ @@ -106,3 +86,8 @@ curl --request POST \ --data 'No one used me for half an hour now. I am going to shutdown soon.' ``` +## Further reading + +* [Messages concepts](basic-messages.html) -- message model and routing +* [WebSocket Messages API](protocol-specification-things-messages.html) -- receive and reply to messages via WebSocket +* [HTTP API Documentation](http-api-doc.html#/Messages) -- full API reference for messages diff --git a/documentation/src/main/resources/pages/ditto/httpapi-overview.md b/documentation/src/main/resources/pages/ditto/httpapi-overview.md index 8f8da92677f..2b95504ddea 100644 --- a/documentation/src/main/resources/pages/ditto/httpapi-overview.md +++ b/documentation/src/main/resources/pages/ditto/httpapi-overview.md @@ -1,48 +1,109 @@ --- -title: HTTP API overview -keywords: api, http, overview, REST +title: API Overview +keywords: API, HTTP, HTTPS, JWT, REST, websocket, WSS tags: [http] permalink: httpapi-overview.html --- -Ditto's HTTP API is documented separately in the [HTTP API Doc](http-api-doc.html). +Ditto provides multiple interfaces for interacting with digital twins, ranging from a REST-like HTTP API to persistent WebSocket connections, Server-Sent Events, and a connectivity layer for integrating external messaging systems. -There you can explore the two different API versions (the difference is described in the -[Basic Overview](basic-overview.html)). +{% include callout.html content="**TL;DR**: You interact with Ditto through its **HTTP API** for request/response operations, the **WebSocket API** for real-time bidirectional communication, **SSE** for streaming change notifications, and the **Connectivity API** for integrating external message brokers." type="primary" %} -Ditto does not provide a fully compliant RESTful API in the -[academic sense](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) as it does not include -hyperlinks in the HTTP responses. -It however tries to follow the other best practices. +## Overview -If you have any feedback on how to improve at that point, Ditto's developer team is [eager to learn](feedback.html). +Ditto offers four primary interfaces, each suited to different use cases: -## Channel +| Interface | Best for | Protocol | +|-----------|----------|----------| +| [HTTP API](http-api-doc.html) | CRUD operations, search, messages | REST-like HTTP/HTTPS | +| [WebSocket API](httpapi-protocol-bindings-websocket.html) | Real-time events, bidirectional messaging | WebSocket (WSS) | +| [Server-Sent Events](httpapi-sse.html) | Streaming change notifications | SSE over HTTP | +| [Connectivity API](connectivity-overview.html) | Integrating message brokers (MQTT, Kafka, AMQP, etc.) | Various | -Ditto supports two types of communication channels: `twin` and `live`. +## How it works -The `twin` channel is used to communicate with the persisted **digital twin** representation of a device. -The `live` channel can be used to communicate directly with an actual device. +### HTTP API -In case the `live` channel is used, the device itself is responsible for answering the command/message. -If no response is returned, the request will result in a timeout, and Ditto will respond with `408 Request Timeout`. -This timeout can be set with the `timeout` parameter. If no `timeout` parameter is set, the default of `10s` is used to -wait for response of the device. -When routing live commands to devices, Ditto is doing an [authorization check](basic-auth.html) based on the policy -of the thing. Ditto also filters responses based on that policy. +The [HTTP API](http-api-doc.html) follows REST conventions and provides a resource-based interface for managing Things, Policies, and Connections. You send standard HTTP requests (`GET`, `PUT`, `POST`, `PATCH`, `DELETE`) and receive JSON responses. -Ditto ensures that the response from the device contains the same `correlation ID`, `entity ID` and `path`. -For the device it is necessary to send back the correlating command response type for the send command. In case there is -a mismatch of the command and command response type Ditto will drop the response from the device and the request will -result in a timeout. +Use the HTTP API when you need: +* Simple request/response interactions +* CRUD operations on Things, Features, Policies, and Connections +* [Searching](httpapi-search.html) across Things +* [Sending messages](httpapi-messages.html) to or from devices +* Lightweight integration from any programming language -The default channel for all HTTP requests is the `twin` channel. The channel can either be set via HTTP header or via -query parameter. +### WebSocket API -For more information about the channel concept see section [Ditto Protocol > Protocol twin/live channel](protocol-twinlive.html). +The [WebSocket API](httpapi-protocol-bindings-websocket.html) provides a persistent, duplex connection using the [Ditto Protocol](protocol-overview.html). You establish a single connection and exchange JSON messages in both directions. -## Content Type +Use the WebSocket API when you need: +* Real-time [change notifications](basic-changenotifications.html) for digital twins +* High-throughput command streams with minimal overhead +* Bidirectional [message](basic-messages.html) exchange +* [Live channel](protocol-twinlive.html) interactions with devices -Currently, the content-type `application/json` is supported for all REST resources except the _PATCH_ resource. -There the content-type has to be `application/merge-patch+json`. +### Server-Sent Events (SSE) +[SSE](httpapi-sse.html) provides a unidirectional stream from Ditto to your client. You open a connection and receive events whenever digital twins change or when streaming search results. + +Use SSE when you need: +* Simple, one-way change notifications without WebSocket complexity +* Streaming search result sets +* Browser-based event consumption via the standard `EventSource` API + +### Connectivity API + +The [Connectivity API](connectivity-overview.html) connects Ditto to external messaging systems like MQTT, Apache Kafka, AMQP 1.0, and AMQP 0.9.1. This enables integration with devices and backend systems that communicate through message brokers. + +Use the Connectivity API when you need: +* Integration with existing messaging infrastructure +* Horizontal scaling of event consumers +* Round-robin dispatching of messages across multiple consumers + +## Channel: twin vs. live + +Ditto supports two communication channels across all interfaces: + +| Channel | Description | +|---------|-------------| +| `twin` (default) | Communicates with the persisted digital twin representation | +| `live` | Communicates directly with the actual device | + +When you use the `live` channel, the device itself handles the command. If the device does not respond within the configured `timeout` (default: 10 seconds), Ditto returns `408 Request Timeout`. Ditto performs [authorization checks](basic-auth.html) and filters responses based on the Thing's Policy. + +For details, see [Ditto Protocol twin/live channel](protocol-twinlive.html). + +## Content type + +The HTTP API supports `application/json` for all resources except `PATCH` operations, which require `application/merge-patch+json`. + +## Feature comparison + +| Feature | HTTP API | WebSocket | SSE | Connectivity | +|---------|----------|-----------|-----|-------------| +| Things management (CRUD) | Yes | Yes | -- | Yes | +| Features management | Yes | Yes | -- | Yes | +| Search | Yes | Yes | Yes (streaming) | -- | +| Count | Yes | -- | -- | -- | +| Messages | Yes (send) | Yes (send + receive) | Yes (receive) | Yes | +| Change notifications | Yes (SSE) | Yes | Yes | Yes | +| Policy-based access control | Yes | Yes | Yes | Yes | +| Horizontal scaling | -- | -- | -- | Yes | + +## Authentication + +All interfaces support: +* **HTTP Basic Authentication** with a user managed by an upstream reverse proxy (e.g., nginx) +* **JSON Web Token (JWT)** issued by an OpenID Connect provider + +See [Authentication](basic-auth.html) for details. + +## Further reading + +* [HTTP API concepts](httpapi-concepts.html) -- versioning, partial updates, conditional requests +* [HTTP API reference](http-api-doc.html) -- full interactive API documentation +* [WebSocket binding](httpapi-protocol-bindings-websocket.html) -- WebSocket-specific protocol details +* [Server-Sent Events](httpapi-sse.html) -- SSE streaming details +* [Connectivity overview](connectivity-overview.html) -- external broker integrations +* [Ditto Protocol](protocol-overview.html) -- the underlying message format diff --git a/documentation/src/main/resources/pages/ditto/httpapi-protocol-bindings-cloudevents.md b/documentation/src/main/resources/pages/ditto/httpapi-protocol-bindings-cloudevents.md index 8d468b0123a..1b6014da97a 100644 --- a/documentation/src/main/resources/pages/ditto/httpapi-protocol-bindings-cloudevents.md +++ b/documentation/src/main/resources/pages/ditto/httpapi-protocol-bindings-cloudevents.md @@ -1,21 +1,21 @@ --- -title: Cloud Events HTTP protocol binding +title: Cloud Events Binding keywords: binding, protocol, http, cloudevents tags: [binding, protocol, http] permalink: httpapi-protocol-bindings-cloudevents.html --- -Implements the [HTTP Protocol Binding for CloudEvents - Version 1.0](https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md). +You use the Cloud Events endpoint to ingest data into Ditto using the standardized [CloudEvents HTTP Protocol Binding (v1.0)](https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md), enabling integration with CloudEvents-aware platforms like Knative. -Unless mentioned otherwise, the endpoint following the Cloud Events specification for the HTTP binding in version 1.0. +{% include callout.html content="**TL;DR**: Send CloudEvents-formatted HTTP requests to `/api/2/cloudevents` with Ditto Protocol JSON as the payload and `ditto:` as the data schema prefix." type="primary" %} -## Cloud Events features +## Overview -The Cloud Events endpoint provides an alternative to the other connectivity APIs to stream data into your instance. +The Cloud Events endpoint provides an alternative to the other connectivity APIs for streaming data into your Ditto instance. It implements the [HTTP Protocol Binding for CloudEvents - Version 1.0](https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md). -## Cloud Events endpoint +## How it works -The Cloud Events endpoint is accessible at the following URL: +### Endpoint ``` http://localhost:8080/api/2/cloudevents @@ -23,38 +23,47 @@ http://localhost:8080/api/2/cloudevents ### Authentication -A user who connects to the Cloud Events endpoint can be authenticated by using +Authenticate using: +* **HTTP Basic Authentication** with a username and password managed by your reverse proxy (e.g., nginx) +* **JSON Web Token (JWT)** issued by an OpenID Connect provider -* HTTP BASIC Authentication by providing a username and the password of a user managed within nginx or -* a JSON Web Token (JWT) issued by an OpenID connect provider. +See [Authentication](basic-auth.html) for details. -See [Authenticate](basic-auth.html) for more details. +### Message format -## Cloud Events protocol format +Your Cloud Event must satisfy these requirements: -The source must be a Cloud Event, encoded in the format for the HTTP binding. It may be encoded in *binary content mode* -or in *structural content mode*. +| Field | Requirement | +|-------|-------------| +| Encoding | Binary content mode or structured content mode | +| Data content type | `application/json` | +| Data schema | Must start with `ditto:` (e.g., `ditto:some-schema`) | +| Payload | [Ditto Protocol JSON](protocol-specification.html) | -The *data content type* of the event must be `application/json`. +## Examples -The *data schema* must start with `ditto:`, for example `ditto:some-schema`. +### Direct HTTP invocation -The events *payload* must in the Ditto Protocol JSON format as defined in the -[Protocol specification](protocol-specification.html). - -## Publishing events to the endpoint +``` +POST /api/2/cloudevents HTTP/1.1 +ce-specversion: 1.0 +ce-type: my.ditto.event +ce-time: 2020-11-24T14:35:00Z +ce-id: f7b197fe-2e59-11eb-a8f4-d45d6455d2cc +ce-source: /my/source +ce-dataschema: ditto:some-schema +Content-Type: application/json; charset=utf-8 -Publishing events to the endpoint can be done by directly sending HTTP requests, conforming to the Cloud Events -HTTP binding specification. Or by using other technologies that have adopted Cloud Events. +{ + ... Ditto Protocol JSON ... +} +``` ### Knative eventing -The endpoint can directly be configured as a [Knative eventing](https://knative.dev/docs/eventing/) destination. +You can configure the Cloud Events endpoint as a [Knative eventing](https://knative.dev/docs/eventing/) destination. This example shows a Knative Sequence that normalizes payloads with a converter before sending them to Ditto: -In the following example, a Knative eventing flow is configured to normalize the payload with a Vorto converter -and send the result to Ditto's cloud events endpoint: - -~~~yaml +```yaml apiVersion: flows.knative.dev/v1 kind: Sequence metadata: @@ -68,37 +77,16 @@ spec: replicationFactor: 1 steps: - ref: - # Convert incoming payload to the Ditto format apiVersion: serving.knative.dev/v1 kind: Service name: vorto-converter namespace: digital-twin reply: - # Deliver to Ditto Cloud Events endpoint uri: http://ditto:ditto@ditto-nginx.digital-twin.svc.cluster.local:8080/api/2/cloudevents -~~~ - -This sequence itself can again be the target of another operation. - -### Direct invocation - -Of course, it is also possible to directly access the Cloud Events endpoint through HTTP: - -An example HTTP request could look like this: - -~~~ -POST /api/2/cloudevents HTTP/1.1 -ce-specversion: 1.0 -ce-type: my.ditto.event -ce-time: 2020-11-24T14:35:00Z -ce-id: f7b197fe-2e59-11eb-a8f4-d45d6455d2cc -ce-source: /my/source -ce-dataschema: ditto:some-schema -Content-Type: application/json; charset=utf-8 +``` -{ - ... Ditto Protocol JSON ... -} -~~~ +## Further reading -For more information, see [HTTP Protocol Binding for CloudEvents - Version 1.0](https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md). +* [HTTP Protocol Binding for CloudEvents - Version 1.0](https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md) +* [Ditto Protocol specification](protocol-specification.html) -- the payload format +* [Knative eventing](https://knative.dev/docs/eventing/) -- event-driven platform diff --git a/documentation/src/main/resources/pages/ditto/httpapi-protocol-bindings-websocket.md b/documentation/src/main/resources/pages/ditto/httpapi-protocol-bindings-websocket.md index 954b0d4454f..1156b5ecb04 100644 --- a/documentation/src/main/resources/pages/ditto/httpapi-protocol-bindings-websocket.md +++ b/documentation/src/main/resources/pages/ditto/httpapi-protocol-bindings-websocket.md @@ -1,74 +1,50 @@ --- -title: WebSocket protocol binding +title: WebSocket Binding keywords: binding, protocol, websocket, http tags: [protocol, http, rql] permalink: httpapi-protocol-bindings-websocket.html --- -[Ditto Protocol](protocol-overview.html) messages can be sent *as is* as [WebSocket](https://tools.ietf.org/html/rfc6455) -message. The Ditto Protocol JSON must be sent as `UTF-8` encoded String payload. +You use the WebSocket binding to send [Ditto Protocol](protocol-overview.html) messages over a persistent, duplex connection -- enabling real-time change notifications, bidirectional messaging, and live device interaction. +{% include callout.html content="**TL;DR**: Connect to `ws://localhost:8080/ws/2`, send Ditto Protocol JSON messages, and subscribe to events, messages, or live commands using plain-text control messages like `START-SEND-EVENTS`." type="primary" %} -## WebSocket features +## Overview -The WebSocket provides an alternative to the [HTTP API](httpapi-overview.html) in order to manage your digital twins. +The WebSocket binding provides an alternative to the [HTTP API](httpapi-overview.html) for managing digital twins. Compared to HTTP, the WebSocket offers: -The benefits of the WebSocket compared to HTTP are multiple ones: +* **Lower overhead** -- a single persistent connection eliminates per-request HTTP handshake costs +* **Duplex communication** -- you receive [change notifications](basic-changenotifications.html), [messages](basic-messages.html), and [live commands/events](protocol-twinlive.html) on the same connection +* **Higher throughput** -- no HTTP header overhead means more commands per second -* a single connection (socket like) is established and for commands to digital twins no further HTTP overhead - (e.g. HTTP headers, HTTP connection establishment) is produced which means you can get more commands/seconds - through the WebSocket compared to the HTTP endpoint -* as the WebSocket is a duplex connection, [change notifications](basic-changenotifications.html) can be sent via the - WebSocket for changes to entities done in Ditto -* additionally, [messages](basic-messages.html) and [live commands/events](protocol-twinlive.html) can be exchanged - (sending and receiving) via multiple connected WebSocket sessions +Every connected WebSocket session receives all events and messages it is authorized to see. There is no round-robin dispatching across sessions with the same authentication. -Please keep in mind that every web WebSocket connection will receive all events and messages it is allowed to receive -depending on the provided authentication.
    -There is no round-robin dispatching for WebSockets using the same authentication. - -{% include warning.html content="This means that WebSockets are not meant to be used for scenarios where horizontal - scaling should be applied. - For those scenarios we suggest using the [Connectivity API](connectivity-overview.html)." +{% include warning.html content="This means that WebSockets are not meant to be used for scenarios where horizontal + scaling should be applied. + For those scenarios we suggest using the [Connectivity API](connectivity-overview.html)." %} -### Send commands and get responses - -When sending a command via WebSocket you will receive a corresponding response (the response can be related to the -request by the `correlation-id` header).
    -The response indicates the success or the failure of the command and, depending on the command type, contains the -result payload. - -Please find examples of commands, and their response pattern at [Protocol examples](protocol-examples.html). - -### Request receiving events/change notifications +## How it works -In addition to the response, which Ditto addresses directly to the instance which was sending the command, an event -is generated.
    -This will be delivered to all other clients with read permissions for the respective thing, feature change, etc. +### Send commands and receive responses -See [request events](#request-events) for subscribing/unsubscribing for receiving change notifications. +Send a Ditto Protocol command as a JSON message and receive a corresponding response. Match responses to requests using the `correlation-id` header. See [Protocol examples](protocol-examples.html) for command/response patterns. -### Request receiving messages +### Subscribe to change notifications -[Messages](basic-messages.html) can be sent both via the [HTTP API](httpapi-overview.html) and the WebSocket. -Receiving messages and answering to them however can only be done via the WebSocket. +After subscribing, you receive [events](basic-changenotifications.html) whenever other clients modify Things, Features, or other entities you have read access to. -See [request messages](#request-messages) for subscribing/unsubscribing for receiving messages. +### Subscribe to messages -### Request receiving live commands + events +[Messages](basic-messages.html) can be sent via both the HTTP API and WebSocket, but you can only **receive and reply** to messages through the WebSocket. -In order to receive [live commands and events](protocol-twinlive.html), the WebSocket API can be used. -The Ditto Protocol messages are the same as for the "twin" channel, only with *live* as channel in the -[topic](protocol-specification-topic.html). +### Subscribe to live commands and events -See [request live commands](#request-live-commands) and [request live events](#request-live-events) for -subscribing/unsubscribing for receiving live commands and events. +Use the WebSocket to receive [live commands and events](protocol-twinlive.html). The Ditto Protocol messages are the same as for the twin channel, but with `live` as the channel in the [topic](protocol-specification-topic.html). +## Setup -## WebSocket endpoint - -The WebSocket endpoint is accessible at the following URL: +### Endpoint ``` ws://localhost:8080/ws/2 @@ -76,26 +52,15 @@ ws://localhost:8080/ws/2 ### Authentication -A user who connects to the WebSocket endpoint can be authenticated by using - -* HTTP BASIC Authentication by providing a username and the password of a user managed within nginx or -* a JSON Web Token (JWT) issued by an OpenID connect provider. - -See [Authenticate](basic-auth.html) for more details. +Authenticate using: +* **HTTP Basic Authentication** with a username and password managed by your reverse proxy (e.g., nginx) +* **JSON Web Token (JWT)** issued by an OpenID Connect provider +See [Authentication](basic-auth.html) for details. ## WebSocket protocol format -As defined in the [Protocol specification](protocol-specification.html) a Ditto Protocol message consists of different -information. This information is combined into a single JSON message for the WebSocket endpoint: - -* [topic](protocol-specification.html#topic): JSON string with key `topic` -* [headers](protocol-specification.html#headers): JSON object with key `headers` -* [path](protocol-specification.html#path): JSON string with key `path` -* [value](protocol-specification.html#value): JSON value (e.g. JSON object, string, array, ...) with key `value` -* [status](protocol-specification.html#status) (for responses): JSON number with key `status` - -The schema for Ditto Protocol message via WebSocket: +A Ditto Protocol message over WebSocket combines all protocol fields into a single JSON object: ```json { @@ -105,129 +70,79 @@ The schema for Ditto Protocol message via WebSocket: "a-header": "
    " }, "path": "", - "value": { - - } + "value": {} } ``` +See the [Protocol specification](protocol-specification.html) for details on [topic](protocol-specification.html#topic), [headers](protocol-specification.html#headers), [path](protocol-specification.html#path), [value](protocol-specification.html#value), and [status](protocol-specification.html#status). -## WebSocket binding specific messages - -The WebSocket binding defines several specific messages which are not defined in the Ditto Protocol specification. - -Those are also not defined as JSON messages, but as plain text messages. All of those declare a demand for some kind -of information from the backend to be pushed into the WebSocket session. - -### Request events - -In order to subscribe for [events/change notifications](basic-changenotifications.html) for entities (e.g. Things), -following text message has to be sent to the backend: `START-SEND-EVENTS` - -From then on the WebSocket session will receive all change notifications it is entitled to see. - -### Request messages - -In order to subscribe for [messages](basic-messages.html) which can be sent from a WebSocket session to another -WebSocket session or from the [HTTP API](httpapi-overview.html) to a WebSocket session, the following text message has -to be sent to the backend: `START-SEND-MESSAGES` - -From then on the WebSocket session will receive all messages it is entitled to see. - -### Request live commands +## Subscription control messages -In order to subscribe for [live commands](protocol-twinlive.html) which can be sent from a WebSocket session to another -WebSocket session, the following text message has to be sent to the backend: `START-SEND-LIVE-COMMANDS` +The WebSocket binding defines plain-text control messages (not JSON) for subscribing to different event streams. -From then on the WebSocket session will receive all live commands it is entitled to see. +### Control message reference -### Request live events +| Description | Subscribe | Unsubscribe | Acknowledgment | +|-------------|-----------|-------------|----------------| +| [Thing change notifications](basic-changenotifications.html) | `START-SEND-EVENTS` | `STOP-SEND-EVENTS` | `START-SEND-EVENTS:ACK` / `STOP-SEND-EVENTS:ACK` | +| [Messages](basic-messages.html) | `START-SEND-MESSAGES` | `STOP-SEND-MESSAGES` | `START-SEND-MESSAGES:ACK` / `STOP-SEND-MESSAGES:ACK` | +| [Live commands](protocol-twinlive.html) | `START-SEND-LIVE-COMMANDS` | `STOP-SEND-LIVE-COMMANDS` | `START-SEND-LIVE-COMMANDS:ACK` / `STOP-SEND-LIVE-COMMANDS:ACK` | +| [Live events](protocol-twinlive.html) | `START-SEND-LIVE-EVENTS` | `STOP-SEND-LIVE-EVENTS` | `START-SEND-LIVE-EVENTS:ACK` / `STOP-SEND-LIVE-EVENTS:ACK` | +| [Policy announcements](protocol-specification-policies-announcement.html) | `START-SEND-POLICY-ANNOUNCEMENTS` | `STOP-SEND-POLICY-ANNOUNCEMENTS` | `START-SEND-POLICY-ANNOUNCEMENTS:ACK` / `STOP-SEND-POLICY-ANNOUNCEMENTS:ACK` | +| Refresh JWT authentication | `JWT-TOKEN` | -- | -- | -In order to subscribe for [live events](protocol-twinlive.html) which can be sent from a WebSocket session to another -WebSocket session, the following text message has to be sent to the backend: `START-SEND-LIVE-EVENTS` +### JWT token refresh -From then on the WebSocket session will receive all live events it is entitled to see. +Ditto closes WebSocket connections when the JWT expires. To keep the connection open, send a new valid JWT using the `JWT-TOKEN` message. The `sub` claim of the new token must match the original token. -### Request policy announcements - -In order to subscribe for [Policy announcements](protocol-specification-policies-announcement.html) which can be -published to a WebSocket session, the following text message has to be sent to the backend: -`START-SEND-POLICY-ANNOUNCEMENTS` - -From then on the WebSocket session will receive all announcements related to policies related to the authenticated -subjects of the websocket session. - -### Overview - -The following table shows which WebSocket protocol message are supported: - -| Description | Request message | Response message | -|-------------|-----------------|------------------| -| Refresh JWT based authentication | `JWT-TOKEN` | `-` | -| Subscribe for [Thing events/change notifications](basic-changenotifications.html) | `START-SEND-EVENTS` | `START-SEND-EVENTS:ACK` | -| Stop receiving Thing change notifications | `STOP-SEND-EVENTS` | `STOP-SEND-EVENTS:ACK` | -| Subscribe for [Thing messages](basic-messages.html) | `START-SEND-MESSAGES` | `START-SEND-MESSAGES:ACK` | -| Stop receiving Thing messages | `STOP-SEND-MESSAGES` | `STOP-SEND-MESSAGES:ACK` | -| Subscribe for [Thing live commands](protocol-twinlive.html) | `START-SEND-LIVE-COMMANDS` | `START-SEND-LIVE-COMMANDS:ACK` | -| Stop receiving Thing live commands | `STOP-SEND-LIVE-COMMANDS` | `STOP-SEND-LIVE-COMMANDS:ACK` | -| Subscribe for [Thing live events](protocol-twinlive.html) | `START-SEND-LIVE-EVENTS` | `START-SEND-LIVE-EVENTS:ACK` | -| Stop receiving Thing live commands | `STOP-SEND-LIVE-EVENTS` | `STOP-SEND-LIVE-EVENTS:ACK` | -| Subscribe for [Policy announcements](protocol-specification-policies-announcement.html) | `START-SEND-POLICY-ANNOUNCEMENTS` | `START-SEND-POLICY-ANNOUNCEMENTS:ACK` | -| Stop receiving Policy announcements | `STOP-SEND-POLICY-ANNOUNCEMENTS` | `STOP-SEND-POLICY-ANNOUNCEMENTS:ACK` | - -### Authentication - -Ditto closes Websocket connections when the JWT provided with the initial connect expires. To keep the connection -open, one can send a valid JWT via `JWT-TOKEN` protocol message. The `sub` of the new token must match the one from -the initial connect, otherwise Ditto will close the connection. - -Ditto expects the message with the JWT as a base64 encoded string provided with the paramter `?jwtToken=`, e.g.: ``` -JWT-TOKEN?jwtToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +JWT-TOKEN?jwtToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ### Enrichment -When extra fields should be added to outgoing messages on the WebSocket channel, an `extraFields` parameter can be -added to the request message. This is supported for all request messages: - -| Description | Request message | [Enrich by extra fields](basic-enrichment.html) | -|-------------|-----------------|------------------| -| Subscribe for [Thing events/change notifications](basic-changenotifications.html) | `START-SEND-EVENTS` | ✔ | -| Subscribe for [Thing messages](basic-messages.html) | `START-SEND-MESSAGES` | ✔ | -| Subscribe for [Thing live commands](protocol-twinlive.html) | `START-SEND-LIVE-COMMANDS` | ✔ | -| Subscribe for [Thing live events](protocol-twinlive.html) | `START-SEND-LIVE-EVENTS` | ✔ | -| Subscribe for [Policy announcements](protocol-specification-policies-announcement.html) | `START-SEND-POLICY-ANNOUNCEMENTS` | ❌ | +Add extra fields to outgoing messages using the `extraFields` parameter on subscription messages: -Analog to the [filtering](#filtering) the parameter is defined like an HTTP query parameter, e.g.: ``` START-SEND-EVENTS?extraFields=attributes/counter,features/ConnectionStatus START-SEND-MESSAGES?extraFields=attributes ``` -### Filtering +Enrichment is supported for all subscription types except Policy announcements. See [enrichment](basic-enrichment.html) for details. -In order to only consume specific events like described in [change notifications](basic-changenotifications.html), the -following parameters can additionally be provided when sending the WebSocket protocol messages: +| Subscription type | Enrichment supported | +|-------------------|---------------------| +| Thing change notifications | Yes | +| Messages | Yes | +| Live commands | Yes | +| Live events | Yes | +| Policy announcements | No | -| Description | Request message | [Filter by namespaces](basic-changenotifications.html#by-namespaces) | [Filter by RQL expression](basic-changenotifications.html#by-rql-expression) | -|-------------|-----------------|------------------|-----------| -| Subscribe for [Thing events/change notifications](basic-changenotifications.html) | `START-SEND-EVENTS` | ✔ | ✔ | -| Subscribe for [Thing messages](basic-messages.html) | `START-SEND-MESSAGES` | ✔ | ❌ | -| Subscribe for [Thing live commands](protocol-twinlive.html) | `START-SEND-LIVE-COMMANDS` | ✔ | ❌ | -| Subscribe for [Thing live events](protocol-twinlive.html) | `START-SEND-LIVE-EVENTS` | ✔ | ✔ | -| Subscribe for [Policy announcements](protocol-specification-policies-announcement.html) | `START-SEND-POLICY-ANNOUNCEMENTS` | ✔ | ❌ | +### Filtering -The parameters are specified similar to HTTP query parameters, the first one separated with a `?` and all following ones -with `&`. You have to URL encode the filter values before using them in a configuration. +You can filter which events you receive by namespace or RQL expression. Specify parameters like HTTP query parameters, with `?` for the first and `&` for subsequent ones. URL-encode filter values before use. -For example this way the WebSocket session would register for all events in the namespace `org.eclipse.ditto` and which -would match an attribute "counter" to be greater than 42: ``` START-SEND-EVENTS?namespaces=org.eclipse.ditto&filter=gt(attributes/counter,42) ``` -The filtering may be also used in combination with an [enrichment](#enrichment), e.g.: +| Subscription type | Filter by namespace | Filter by RQL | +|-------------------|---------------------|---------------| +| Thing change notifications | Yes | Yes | +| Messages | Yes | No | +| Live commands | Yes | No | +| Live events | Yes | Yes | +| Policy announcements | Yes | No | + +You can combine filtering with enrichment: + ``` START-SEND-EVENTS?extraFields=attributes&filter=gt(attributes/counter,42) ``` + +## Further reading + +* [Ditto Protocol overview](protocol-overview.html) -- message format specification +* [Protocol examples](protocol-examples.html) -- command and response patterns +* [Change notifications](basic-changenotifications.html) -- event filtering concepts +* [Connectivity API](connectivity-overview.html) -- for horizontally scaled event consumption diff --git a/documentation/src/main/resources/pages/ditto/httpapi-search.md b/documentation/src/main/resources/pages/ditto/httpapi-search.md index 9f3575a3498..d16ba36fb0c 100644 --- a/documentation/src/main/resources/pages/ditto/httpapi-search.md +++ b/documentation/src/main/resources/pages/ditto/httpapi-search.md @@ -1,124 +1,88 @@ --- -title: HTTP API search +title: HTTP API Search keywords: http, api, search, query, rql tags: [http, search, rql] permalink: httpapi-search.html --- -The [search aspect](basic-search.html) of Ditto can be accessed via an HTTP API. +You search for Things using [RQL expressions](basic-rql.html) against the HTTP search endpoint, with support for filtering, sorting, paging, and field selection. -{% include note.html content="Find the HTTP API reference at the +{% include callout.html content="**TL;DR**: Send `GET` or `POST` requests to `/api/2/search/things` with `filter`, `option`, `fields`, and `namespaces` parameters to find and retrieve Things matching your criteria." type="primary" %} + +{% include note.html content="Find the HTTP API reference at the [Search resources](http-api-doc.html?urls.primaryName=api2#/Search)." %} -The concepts of the [RQL expression](basic-rql.html#rql-filter), [RQL sorting](basic-rql.html#rql-sorting) and -[RQL paging](basic-search.html#rql-paging-deprecated) are mapped to HTTP as +## Overview -* query parameters which are added to `GET` requests to the search endpoint; -* a x-www-form-urlencoded body which is added to `POST` requests to the search endpoint. +The search endpoint is: ``` http://localhost:8080/api/2/search/things ``` -If the `filter` parameter is omitted, the result contains all `Things` the authenticated user is -[allowed to read](basic-auth.html). +If you omit the `filter` parameter, the result contains all `Things` the authenticated user is [allowed to read](basic-auth.html). For details on the underlying search concepts, see [Basic Search](basic-search.html). -Optionally a `namespaces` parameter can be added to search only in the given namespaces. +## How it works -## GET -### Query parameters +You pass search criteria as query parameters (for `GET`) or as a `x-www-form-urlencoded` body (for `POST`): -In order to define for which `Things` to search, the `filter` query parameter has to be added. -In order to change the sorting and limit the result (also to do paging), the `option` parameter has to be added. -Default values of each option is documented [here](basic-search.html#sorting-and-paging-options). +| Parameter | Purpose | +|-----------|---------| +| `filter` | [RQL filter expression](basic-rql.html#rql-filter) to select Things | +| `option` | [RQL sorting and paging](basic-search.html#sorting-and-paging-options) options | +| `fields` | [Field selector](httpapi-concepts.html#partial-requests) for response projection | +| `namespaces` | Comma-separated list of namespaces to search within | -Complex example: -``` -GET .../search/things?filter=eq(attributes/location,"living-room")&option=sort(+thingId),limit(0,5)&namespaces=org -.eclipse.ditto,foo.bar -``` +## Examples -Another Complex example with the `namespaces` parameter: -``` -GET .../search/things?filter=eq(attributes/location,"living-room")&namespaces=org.eclipse.ditto,foo.bar -``` +### Search with GET -The HTTP search API can also profit from the [partial request](httpapi-concepts.html#partial-requests) concept -of the API: -Additionally to a `filter` and `options`, a `fields` parameter may be specified in order to select which data -of the result set to retrieve. +Find Things in the living room, sorted by ID, limited to 5 results: -Example which only returns `thingId` and the `manufacturer` attribute of the found Things: ``` -GET .../search/things?filter=eq(attributes/location,"living-room")&fields=thingId,attributes/manufacturer +GET .../search/things?filter=eq(attributes/location,"living-room")&option=sort(+thingId),limit(0,5) ``` -With the `namespaces` parameter, the result can be limited to the given namespaces. +Restrict search to specific namespaces: -Example which only returns Things with the given namespaces prefix: ``` -GET .../search/things?namespaces=org.eclipse.ditto,foo.bar +GET .../search/things?filter=eq(attributes/location,"living-room")&namespaces=org.eclipse.ditto,foo.bar ``` -### Search count -Search counts can be made against this endpoint: - -``` -http://localhost:8080/api/2/search/things/count -``` +Return only the `thingId` and `manufacturer` attribute: -Complex example: ``` -GET .../search/things/count?filter=eq(attributes/location,"living-room") +GET .../search/things?filter=eq(attributes/location,"living-room")&fields=thingId,attributes/manufacturer ``` -## POST -### x-www-form-urlencoded +### Search with POST -In order to define for which `Things` to search, the key `filter` has to be used. -In order to change the sorting and limit the result (also to do paging), the key `option` has to be used. -Default values of each option is documented [here](basic-search.html#sorting-and-paging-options). +`POST` requests accept the same parameters as `x-www-form-urlencoded` body content: -Complex example: ``` POST .../search/things -body: filter=eq(attributes/location,"living-room")&namespaces=org.eclipse.ditto,foo.bar&option=sort(+thingId),limit(0,5) +body: filter=eq(attributes/location,"living-room")&option=sort(+thingId),limit(0,5) ``` -Another Complex example with the `namespaces` parameter: -``` -POST .../search/things -body: filter=eq(attributes/location,"living-room")&namespaces=org.eclipse.ditto,foo.bar -``` +Use `POST` when your query string would be too long for a URL. -The HTTP search API can also profit from the [partial request](httpapi-concepts.html#partial-requests) concept -of the API: -Additionally to a `filter` and `options`, the key `fields` may be specified in order to select which data -of the result set to retrieve. +### Count Things -Example which only returns `thingId` and the `manufacturer` attribute of the found Things: -``` -POST .../search/things -body: filter=eq(attributes/location,"living-room")&fields=thingId,attributes/manufacturer -``` - -With the `namespaces` parameter, the result can be limited to the given namespaces. +Get the number of Things matching a filter: -Example which only returns Things with the given namespaces prefix: ``` -POST .../search/things -body: namespaces=org.eclipse.ditto,foo.bar +GET .../search/things/count?filter=eq(attributes/location,"living-room") ``` -### Search count -Search counts can be made against this endpoint: - -``` -http://localhost:8080/api/2/search/things/count -``` +Or with `POST`: -Complex example: ``` POST .../search/things/count body: filter=eq(attributes/location,"living-room") ``` + +## Further reading + +* [Basic Search](basic-search.html) -- search concepts and indexing +* [RQL expressions](basic-rql.html) -- filter and sort syntax +* [HTTP API concepts: partial requests](httpapi-concepts.html#partial-requests) -- field selectors diff --git a/documentation/src/main/resources/pages/ditto/httpapi-sse.md b/documentation/src/main/resources/pages/ditto/httpapi-sse.md index 632a3d72217..10ab9197b3d 100644 --- a/documentation/src/main/resources/pages/ditto/httpapi-sse.md +++ b/documentation/src/main/resources/pages/ditto/httpapi-sse.md @@ -1,73 +1,50 @@ --- -title: HTTP API server sent events (SSE) +title: Server-Sent Events (SSE) keywords: http, api, sse, EventSource, fields, projection, extra, enrich tags: [http, rql] permalink: httpapi-sse.html --- -Server-Sent Events (SSEs) -can be used to get notified when the state of **digital twins** change, and to receive a -[search results](basic-search.html) stream. +You use Server-Sent Events (SSE) to receive real-time change notifications for digital twins and to stream search results -- all through a simple, unidirectional HTTP connection. -## Server-Sent Events +{% include callout.html content="**TL;DR**: Open an SSE connection to `/api/2/things` with `Accept: text/event-stream` to stream change notifications, or to `/api/2/search/things` to stream search results. Filter with `namespaces`, `filter`, `fields`, and `extraFields` parameters." type="primary" %} -Server-Sent Events are unidirectional originating from the back-end towards the client. Via SSEs -the client can only be notified, it cannot send data back (it can use plain HTTP for that). +## Overview -For a detailed introduction into SSEs, please visit -the [HTML5 specification](https://html.spec.whatwg.org/multipage/server-sent-events.html). +Server-Sent Events (SSEs) provide a unidirectional stream from Ditto to your client. Unlike WebSockets, you cannot send data back through the same connection (use plain HTTP for that). -### SSEs in JavaScript +SSE is simpler than WebSocket to set up and works natively with the browser `EventSource` API. The events use the same JSON structure as the HTTP API responses. -Using the `EventSource` object in JavaScript is also covered in the [HTML5 specification](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events-intro). +For a detailed introduction to SSE, see the [HTML5 specification](https://html.spec.whatwg.org/multipage/server-sent-events.html). -## SSE API `/things` +## SSE for Thing change notifications + +### Endpoint -The SSE API for receiving [change notifications](basic-changenotifications.html) is the `/things` endpoint: ``` http://localhost:8080/api/2/things ``` -This is a mechanism to get [change notifications](basic-changenotifications.html). -The benefit of this mechanism in comparison to the [WebSocket](httpapi-protocol-bindings-websocket.html) channel is, -that it is even easier to open a SSE connection from the client than a WebSocket, -and that in Ditto's interpretation of SSEs the events sent back from the backend have the same JSON structure as -the HTTP API on which they are invoked. - -When the endpoint is invoked with an HTTP header `Accept` with value `text/event-stream`, a Server-Sent Event stream of -[change notifications](basic-changenotifications.html) is created by Ditto and for each notification for which the -caller has READ permissions (see [authorization](basic-auth.html#authorization)), an event is sent to the client. - -The format of the event at the `/things` endpoint is always in the form of a [Thing JSON](basic-thing.html#model-specification). +When you call this endpoint with `Accept: text/event-stream`, Ditto opens an SSE stream and sends a [change notification](basic-changenotifications.html) for every Thing modification that you have `READ` permission to see. -For partial updates to a `Thing` however, only the changed part is sent back via the SSE, not the complete `Thing`. +Events arrive in [Thing JSON](basic-thing.html#model-specification) format. For partial updates, only the changed portion is sent -- not the complete `Thing`. +### Filtering notifications -### Only get notified about specific changes +You can combine all of the following parameters to narrow what events you receive. -In order to apply a server side filtering of which Server-Sent Events should be emitted to a consumer, Ditto provides -several possibilities listed in the sections below. - -All of the query parameters below can be combined, so that you can for example express that only events from -a certain namespace with a specific RQL expression should be emitted, which could look like: -``` -http://localhost:8080/api/2/things?namespaces=org.eclipse.ditto.one,org.eclipse.test&filter=gt -(attributes/counter,42) -``` +#### By Thing IDs -#### Specify the IDs of the Things +Watch only specific Things: -When the `/things` endpoint is used for connecting to the SSE stream, all -things visible for the authenticated user are -included in the stream. If only specific things should be watched, the query parameter `ids` can be added: ``` http://localhost:8080/api/2/things?ids=, ``` -#### Fields projection +#### By field projection + +Watch only specific fields using the [partial request](httpapi-concepts.html#partial-requests) `fields` parameter: -Additionally, using the `fields` parameter of the [partial request](httpapi-concepts.html#partial-requests) feature, -only specific parts can be watched for changes, e.g.: ``` http://localhost:8080/api/2/things?fields=thingId,attributes ``` @@ -76,71 +53,60 @@ http://localhost:8080/api/2/things?fields=thingId,attributes ##### Projecting `_context` -One special field which can be projected for the SSE is the `_context` field. -As the SSE format returns a "normalized" view in form of the [thing JSON](basic-thing.html#model-specification) -of change events, some "context" of the event gets lost, e.g. on which `path` the event was issued or whether it was -a "merged" or "modified" event. +Select the `_context` field to receive metadata about each event: -To obtain this kind of context information, select the `_context` field as part of `fields`, e.g.: ``` http://localhost:8080/api/2/things?fields=thingId,attributes,_context ``` -The `_context` will contain: -* `topic`: the [Ditto Protocol topic](protocol-specification.html#topic) -* `path`: the [Ditto Protocol path](protocol-specification.html#path) -* `value`: the [Ditto Protocol value](protocol-specification.html#value) -* `headers`: the [Ditto Protocol headers](protocol-specification.html#headers) (even all headers) of the command which caused the event +The `_context` includes: +* `topic` -- the [Ditto Protocol topic](protocol-specification.html#topic) +* `path` -- the [Ditto Protocol path](protocol-specification.html#path) +* `value` -- the [Ditto Protocol value](protocol-specification.html#value) +* `headers` -- the [Ditto Protocol headers](protocol-specification.html#headers) -It is also possible to select only specific context information, e.g.: `fields=_context/topic,_context/path,_context/value` -in order to exclude the `headers`. +You can also select specific context fields: `fields=_context/topic,_context/path,_context/value` +#### By field enrichment -#### Field enrichment +Add [extra fields](basic-enrichment.html) to each event beyond what actually changed: -In addition to the fields projection, one can also choose to select [extra fields](basic-enrichment.html) -to return in addition to the actually changed fields, e.g.: ``` http://localhost:8080/api/2/things?extraFields=attributes ``` -The result is, that the server-sent events are merged, i.e. the SSE contains the actually changed data + the extra fields. +Combine with RQL filtering to filter on enriched fields: -This can be used in combination with the below mentioned [RQL filter](#filtering-by-rql-expression), e.g.: ``` http://localhost:8080/api/2/things?extraFields=attributes/location&filter=eq(attributes/location,"kitchen") ``` -For combined usage of `fields` and `extraFields` one needs to specify all fields, selected as extra fields, for the -field projection, too. This is required to allow filtering based on extra fields but still omit them in the payload. -An example without filtering would look like this: +When using both `fields` and `extraFields`, include the extra fields in the `fields` list if you want them in the response: + ``` http://localhost:8080/api/2/things?fields=thingId,attributes&extraFields=attributes ``` +#### By namespace -#### Filtering by namespaces +Receive events only from specific namespaces: -As described in [change notifications](basic-changenotifications.html#by-namespaces), it is possible to subscribe only -for changes done in specific namespaces. At the SSE API, simply specify the `namespaces` parameter and provide a comma -separated list of which namespaces to select, e.g.: ``` http://localhost:8080/api/2/things?namespaces=org.eclipse.ditto.one,org.eclipse.test ``` -#### Filtering by RQL expression +#### By RQL expression + +Filter events with an [RQL expression](basic-rql.html): -As also described in [change notifications](basic-changenotifications.html#by-rql-expression), it is additionally -possible to specify an RQL expression expressing on which occasions to emit an event via the SSE API. -Simply specify the `filter` parameter with an [RQL expression](basic-rql.html), e.g.: ``` http://localhost:8080/api/2/things?filter=gt(attributes/counter,42) ``` +### Example: JavaScript EventSource -### Example for SSE on Things +Assuming a Thing with the following content: -Assuming we have a thing with the following JSON content: ```json { "thingId": "org.eclipse.ditto:fancy-thing", @@ -163,196 +129,118 @@ Assuming we have a thing with the following JSON content: } ``` -From within JavaScript we can now create an `EventSource` in order to open up a SSE stream in Ditto and simply print -each event to the console. This one tracks only changes to the thing with ID `org.eclipse.ditto:fancy-thing` and -only watches for changes on the feature `lamp`: +Create an `EventSource` to stream changes for this Thing's `lamp` feature: + ```javascript -// the javascript must be served from the same domain as Ditto is running in order to avoid CORS problems -let source = new EventSource('/api/2/things?ids=org.eclipse.ditto:fancy-thing&fields=thingId,features/lamp', { withCredentials: true }); +// the JavaScript must be served from the same domain as Ditto to avoid CORS issues +let source = new EventSource( + '/api/2/things?ids=org.eclipse.ditto:fancy-thing&fields=thingId,features/lamp', + { withCredentials: true } +); source.onmessage = function (event) { console.log(event.data); }; ``` -By defining `{ withCredentials: true }` at the `new EventSource()`, the browser credentials (`Authorization` header) of -the already authenticated browser against that domain are sent along, this works for Basic Auth as well as for -JWT based authentication using a `Bearer` token. +Setting `{ withCredentials: true }` sends browser credentials (Basic Auth or JWT `Bearer` token) with the request. -This would log the changed content of each thing the authenticated subject is allowed to `READ`. +When the `on` property changes via `PUT /api/2/things/org.eclipse.ditto:fancy-thing/features/lamp/properties/on`, the EventSource receives: -So when the `on` property of the `lamp` feature is changed to `true` via such an HTTP API call: -``` -PUT /api/2/things/org.eclipse.ditto:fancy-thing/features/lamp/properties/on -payload: true -``` - -the JavaScript snippet would log to console: ```json { "thingId": "org.eclipse.ditto:fancy-thing", "features": { "lamp": { - "properties": { - "on": false - } + "properties": { "on": false } } } } ``` -## SSE API `/things/` +## SSE for a single Thing -### Subscribe for changes of a single Thing +### Watch all changes to one Thing -Opening the SSE on the `/things/` endpoint is used for connecting to the SSE stream in order to watch changes -for only one specific thing: ``` http://localhost:8080/api/2/things/ ``` -Example - only get the changes of the thing `org.eclipse.ditto:thing-1`: -``` -http://localhost:8080/api/2/things/org.eclipse.ditto:thing-1 -``` - -### Subscribe for changes of a specific path inside a Thing +### Watch a specific path within a Thing -Opening the SSE on the `/things//` endpoint is used for connecting to the SSE stream in -order to watch changes for only one specific thing on only a specific path in that thing: ``` -http://localhost:8080/api/2/things// +http://localhost:8080/api/2/things//attributes/location ``` -The data contained in the event will be the same as when the endpoint would be queried via a normal HTTP `GET`. -Example - only get the value of the attribute `location` whenever that changes: -``` -http://localhost:8080/api/2/things/org.eclipse.ditto:thing-1/attributes/location -``` +## SSE for messages + +### Thing messages -### Subscribe for messages for a specific Thing +Subscribe to messages sent to or from a Thing: -Opening the SSE on the `/things//inbox/messages` or `/things//outbox/messages` endpoint is used for -connecting to the SSE stream in order to receive [messages](basic-messages.html) sent to or sent by the thing: ``` http://localhost:8080/api/2/things//inbox/messages http://localhost:8080/api/2/things//outbox/messages ``` -In order to subscribe just for a specific [message subject](basic-messages.html#elements), just use the same path as -you would for e.g. sending the message via HTTP `POST`: -``` -http://localhost:8080/api/2/things//inbox/messages/ -http://localhost:8080/api/2/things//outbox/messages/ -``` +Filter by [message subject](basic-messages.html#message-elements): -Example - receive whenever a device sends a "smoke-alarm" message to its outbox: ``` -http://localhost:8080/api/2/things/org.eclipse.ditto:thing-1/outbox/messages/smoke-alarm +http://localhost:8080/api/2/things//outbox/messages/smoke-alarm ``` -### Subscribe for messages of a specific Feature of a specific Thing +### Feature messages + +Subscribe to messages for a specific Feature: -Opening the SSE on the `/things//features//inbox/messages` or -`/things//features///outbox/messages` endpoint is used for -connecting to the SSE stream in order to receive [messages](basic-messages.html) sent to or sent by the thing feature: ``` http://localhost:8080/api/2/things//features//inbox/messages http://localhost:8080/api/2/things//features//outbox/messages ``` -In order to subscribe just for a specific [message subject](basic-messages.html#elements), just use the same path as -you would for e.g. sending the message via HTTP `POST`: -``` -http://localhost:8080/api/2/things//features//inbox/messages/ -http://localhost:8080/api/2/things//features//outbox/messages/ -``` +Filter by subject: -Example - receive whenever a device sends a "ping" message to the outbox of its "Pinger" feature: ``` -http://localhost:8080/api/2/things/org.eclipse.ditto:thing-1/features/Pinger/outbox/messages/ping +http://localhost:8080/api/2/things//features/Pinger/outbox/messages/ping ``` +## SSE for search results -## SSE API `/search/things` +### Endpoint -The SSE API to stream search results is the `/search/things` endpoint: ``` http://localhost:8080/api/2/search/things ``` -This is the second mechanism of Ditto in order to get [search results](basic-search.html). -The benefits of this mechanism over the [search protocol](protocol-specification-things-search.html) are: -- The client side is easy to implement; it needs not abide by the reactive-streams rules. -- SSE permits [resuming a stream from the last received ID](#resuming-by-last-event-id) after connection interruptions. +This streams [search results](basic-search.html) as SSE events. Compared to the [search protocol](protocol-specification-things-search.html): +* **Simpler client implementation** -- no need to implement reactive-streams rules +* **Resumable** -- supports the `Last-Event-ID` header to resume interrupted streams +* **No application-layer flow control** -- relies on TCP for back-pressure -The drawback is, that SSE has no application-layer flow control and must rely on the transport layer (TCP) for -back-pressure. In contrast, the [search protocol](protocol-specification-things-search.html) supports back-pressure -and cancellation over any transport layer by reactive-streams means. +### Filtering and sorting -When the endpoint is invoked with an HTTP header `Accept` with value `text/event-stream`, a Server-Sent Event stream of -things is created by Ditto and for each thing matching the search filter for which the caller has READ permissions -(see [authorization](basic-auth.html#authorization)), an event is sent to the client. +Apply the same parameters as the regular search endpoint: -The format of the event at the `/search/things` endpoint is always in the form of a [Thing JSON](basic-thing.html#model-specification) -(in API 1 format or API 2 format depending on which endpoint the SSE was created). - -### Filtering by RQL expression - -Specify the `filter` parameter with an [RQL expression](basic-rql.html) to restrict the search results to things -matching the RQL expression. For example, the SSE stream below emits only things which have a `counter` attribute -with value `42`: ``` http://localhost:8080/api/2/search/things?filter=eq(attributes/counter,42) -``` - -### Filtering by namespaces - -Specify the `namespaces` parameter to restrict search to the namespaces given as a comma separated list. For example: -``` http://localhost:8080/api/2/search/things?namespaces=org.eclipse.ditto.one,org.eclipse.test -``` - -### Sorting by RQL sort option - -Specify the `option` parameter with an [RQL sort option](basic-rql.html#rql-sorting) to stream things in a certain -order. For example, the SSE stream below emits things according to the timestamp. The timestamp of their last updates -is stored in the `_modified` field, and `-` describes the descending order, thus the thing with the newest -changes appears first: -``` http://localhost:8080/api/2/search/things?option=sort(-_modified) -``` - -**Fields projection** - -Use the `fields` parameter to retrieve only specific parts of things in search results, e.g.: -``` http://localhost:8080/api/2/search/things?fields=thingId,attributes ``` -### Resuming by `Last-Event-ID` +### Resuming with Last-Event-ID -The [HTML5 SSE specification](https://html.spec.whatwg.org/multipage/server-sent-events.html) -permits clients to resume from interrupted streams by sending a header `Last-Event-ID`. -Each thing in the search result has its thing ID set as the event ID. -To resume the stream from the point of its interruption, -start another SSE stream with _identical_ query parameters and the `Last-Event-ID` header set to the last received -event ID. -Specification-conform SSE clients perform resumption automatically, making SSE a simple way to export large numbers -of things over a slow connection for long periods of time. +Each search result event includes the Thing ID as its event ID. To resume an interrupted stream, send the `Last-Event-ID` header with the last received ID. Specification-conformant SSE clients handle this automatically. - -**Example:** - -Request ``` GET http://localhost:8080/api/2/search/things?fields=thingId&option=sort(+thingId) HTTP/1.1 Accept: text/event-stream Last-Event-ID: ditto:device7152 ``` -Response +Response: + ``` -HTTTP/1.1 200 OK +HTTP/1.1 200 OK Content-Type: text/event-stream data:{"thingId":"ditto:device7153"} @@ -364,3 +252,10 @@ id:ditto:device7154 data:{"thingId":"ditto:device7155"} id:ditto:device7155 ``` + +## Further reading + +* [Change notifications](basic-changenotifications.html) -- filtering concepts +* [Enrichment](basic-enrichment.html) -- adding extra fields to events +* [Basic Search](basic-search.html) -- search concepts +* [WebSocket binding](httpapi-protocol-bindings-websocket.html) -- duplex alternative to SSE diff --git a/documentation/src/main/resources/pages/ditto/installation-extending.md b/documentation/src/main/resources/pages/ditto/installation-extending.md index e01833d7aea..021ffec7a17 100644 --- a/documentation/src/main/resources/pages/ditto/installation-extending.md +++ b/documentation/src/main/resources/pages/ditto/installation-extending.md @@ -5,28 +5,35 @@ keywords: running, start, run, docker, docker-compose, extension, custom, config permalink: installation-extending.html --- -## Create Extensions for Ditto -Ditto offers the possibility to execute custom behaviour by utilizing Pekko extensions. The places which can be -extended by such custom behaviour are marked by extending the `DittoExtensionPoint` interface. Add a new -implementation of an interface extending `DittoExtensionPoint` for changing its behaviour. +You extend Ditto by implementing custom `DittoExtensionPoint` interfaces and loading them into the service via Pekko's classloader. + +{% include callout.html content="**TL;DR**: Create a Java class implementing a `DittoExtensionPoint` interface, configure it in a `-extension.conf` file, and add the JAR to `/opt/ditto/extensions` in the Docker container." type="primary" %} + +## Overview + +Ditto provides extension points throughout its codebase, marked by interfaces that extend `DittoExtensionPoint`. You can replace the default behavior at these points by providing your own implementation. + +## How it works + +### Creating an extension + +Your implementation needs a public constructor that accepts an `ActorSystem` and `Config` parameter, which Pekko's classloader uses for reflection-based instantiation: -The implementation needs a public constructor accepting an ActorSystem and Config, for the Pekko Classloader to load -the extension via reflection. ```java public CustomExtension(final ActorSystem actorSystem, final Config config) {} ``` -## Configure Extensions -In order for the Pekko Classloader to load the correct implementation of a `DittoExtensionPoint`, the -implementation has to be configured. This can be done by adding the `CONFIG_KEY` of the extension either to the -`-extension.conf` if the extension should only be loaded in specific services, or to the `reference.conf` -for a global scope. +### Configuring an extension -The configuration for an extension consists of two parts: -* `extension-class`: specify the implementation that should be used by the canonical name. -* `extension-config`: specify custom configurations for the extension. +Tell Pekko's classloader which implementation to use by adding the extension's `CONFIG_KEY` to: +* A `-extension.conf` file for service-specific extensions +* The `reference.conf` for a global scope -``` +Each extension configuration has two parts: +* `extension-class`: The fully qualified class name of your implementation +* `extension-config`: Custom configuration for the extension (optional) + +```hocon ditto.extensions.signal-enrichment-provider { extension-class = org.eclipse.ditto.gateway.service.endpoints.utils.DefaultGatewaySignalEnrichmentProvider extension-config = { @@ -39,39 +46,38 @@ ditto.extensions.signal-enrichment-provider { } ``` -If no custom configuration is needed, the `extension-config` can be omitted, thus directly specifying the -implementation. +If your extension needs no custom configuration, use the shorthand form: -``` +```hocon ditto.extensions.signal-enrichment-provider = org.eclipse.ditto.gateway.service.endpoints.utils.DefaultGatewaySignalEnrichmentProvider ``` -## Extend Ditto Docker images +## Configuration + +### Adjusting service configuration + +For simple configuration changes, use [system properties](operating-configuration.html). For extensive changes, create a [HOCON](https://github.com/lightbend/config/blob/main/HOCON.md) file named `-extension.conf` and place it in the Docker container's working directory: -### Adjusting configuration of Ditto -Adjusting configuration is also possible using [system properties](installation-operating.html#ditto-configuration). -If however lots of configuration changes should be done, a more feasible approach is to provide a -[HOCON](https://github.com/lightbend/config/blob/main/HOCON.md) formatted configuration file named -`-extension.conf` in the working directory of Ditto's Docker container: - -* for extending Ditto's Policies service: `/opt/ditto/policies-extension.conf` -* for extending Ditto's Things service: `/opt/ditto/things-extension.conf` -* for extending Ditto's Search service: `/opt/ditto/search-extension.conf` -* for extending Ditto's Connectivity service: `/opt/ditto/connectivity-extension.conf` -* for extending Ditto's Gateway service: `/opt/ditto/gateway-extension.conf` +| Service | Extension config path | +|---------|----------------------| +| Policies | `/opt/ditto/policies-extension.conf` | +| Things | `/opt/ditto/things-extension.conf` | +| Search | `/opt/ditto/search-extension.conf` | +| Connectivity | `/opt/ditto/connectivity-extension.conf` | +| Gateway | `/opt/ditto/gateway-extension.conf` | -Those configuration files can contain any [Ditto configuration](installation-operating.html#ditto-configuration) done in -the service config files. +These files can contain any configuration from the [service config files](operating-configuration.html). For example, the [gateway.conf](https://github.com/eclipse-ditto/ditto/blob/master/gateway/service/src/main/resources/gateway.conf) -contains the following configuration snippet: +contains the following health-check configuration: + ```hocon ditto { gateway { health-check { cluster-roles = { enabled = true - enabled = ${?HEALTH_CHECK_ROLES_ENABLED} # may be overridden with this environment variable + enabled = ${?HEALTH_CHECK_ROLES_ENABLED} expected = [ "policies", @@ -86,9 +92,9 @@ ditto { } ``` -If this needs to be adjusted, e.g. because the "connectivity" role should not be checked in the health-check -(which could be the case if `ditto-connectivity` should not be started at all in a Ditto installation), this would be -possible by creating a `gateway-extension.conf` and adding the following content: +To remove the "connectivity" role from the health check (e.g. when not starting `ditto-connectivity` +at all), create a `gateway-extension.conf` with: + ```hocon ditto.gateway.health-check.cluster-roles = { expected = [ @@ -100,48 +106,51 @@ ditto.gateway.health-check.cluster-roles = { } ``` -And then by putting this `gateway-extension.conf`, e.g. as a Docker volume mount, to the path `/opt/ditto/gateway-extension.conf`. +Then mount the file into the container at `/opt/ditto/gateway-extension.conf`. + +### Adding JARs to the classpath + +Ditto Docker images automatically add all JARs from these directories to the classpath: -### Providing additional functionality by adding `.jars` to the classpath -The new extensions and their corresponding configuration have to be in the Java classpath of the Ditto service which -loads them. To achieve this, the Ditto Docker images automatically add all jars, that are in the home directory of -the docker container into the classpath: * `/opt/ditto` * `/opt/ditto/extensions` -The easiest way to achieve this, is thus building an -extension jar (including the extension classes and extension config files) and adding it to the home `extensions` -directory of the Docker container. - -## Example -* Create a new implementation of the `CustomApiRoutesProvider`, overriding the `unauthorized(*)` and - `authorized(*)` functions, returning custom HTTP API routes. -* Build the project to a new `gateway-extension.jar` -* Add the `gateway-extension.jar` to the `/opt/ditto/extensions` directory of the Docker images, by i.e. copying the jar - into the container. - ``` - docker cp gateway-extension.jar container_id:/opt/ditto/extensions/ - ``` -* Configure the new `CustomApiRoutesProvider` via a file `gateway-extension.conf` - ``` - ditto.extensions.custom-api-routes-provider = org.company.project.gateway.service.endpoints.utils.MyCustomApiRoutesProvider - ``` -* Add the `gateway-extension.conf` to the `/opt/ditto` directory of the Docker images - ``` - docker cp gateway-extension.conf container_id:/opt/ditto/ - ``` - -Alternatively, you can of course also mount the extension `.jar` and `.conf` file into the Docker containers, e.g. -via docker-compose: +Build your extension as a JAR (including extension classes and config files) and place it in the `extensions` directory. + +## Examples + +### Custom API routes + +1. Create a new implementation of `CustomApiRoutesProvider`, overriding the `unauthorized(*)` and `authorized(*)` functions to return custom HTTP API routes. +2. Build the project into a `gateway-extension.jar`. +3. Add the JAR to the container: + ```bash + docker cp gateway-extension.jar container_id:/opt/ditto/extensions/ + ``` +4. Create a `gateway-extension.conf`: + ```hocon + ditto.extensions.custom-api-routes-provider = org.company.project.gateway.service.endpoints.utils.MyCustomApiRoutesProvider + ``` +5. Add the config to the container: + ```bash + docker cp gateway-extension.conf container_id:/opt/ditto/ + ``` + +Alternatively, mount both files via docker-compose: + ```yaml - connectivity: - image: docker.io/eclipse/ditto-gateway:${DITTO_VERSION:-latest} - ... - environment: - - TZ=Europe/Berlin - - JAVA_TOOL_OPTIONS=-Dlogback.configurationFile=/opt/ditto/logback.xml - volumes: - - ./gateway-extension.conf:/opt/ditto/gateway-extension.conf - - ./logback.xml:/opt/ditto/logback.xml - - ./gateway-extension.jar:/opt/ditto/extensions/gateway-extension.jar -``` \ No newline at end of file +connectivity: + image: docker.io/eclipse/ditto-gateway:${DITTO_VERSION:-latest} + environment: + - TZ=Europe/Berlin + - JAVA_TOOL_OPTIONS=-Dlogback.configurationFile=/opt/ditto/logback.xml + volumes: + - ./gateway-extension.conf:/opt/ditto/gateway-extension.conf + - ./logback.xml:/opt/ditto/logback.xml + - ./gateway-extension.jar:/opt/ditto/extensions/gateway-extension.jar +``` + +## Further reading + +* [Operating - Configuration](operating-configuration.html) +* [Architecture Overview](architecture-overview.html) diff --git a/documentation/src/main/resources/pages/ditto/installation-operating.md b/documentation/src/main/resources/pages/ditto/installation-operating.md index f63e659ac9f..ec4d40dcf5c 100644 --- a/documentation/src/main/resources/pages/ditto/installation-operating.md +++ b/documentation/src/main/resources/pages/ditto/installation-operating.md @@ -9,16 +9,16 @@ permalink: installation-operating.html Once you have successfully started Ditto, proceed with setting it up for continuous operation. -This page shows the basics for operating Ditto. +This page covers the basics for operating Ditto. ## Configuration -Ditto has many config parameters which can be set in the config files or via environment variables. -This section will cover some of Ditto's config parameters. +You configure Ditto through config files or environment variables. +This section covers key configuration parameters. ### MongoDB configuration -If you choose not to use the MongoDB container and instead use a dedicated MongoDB you can use -the following environment variables in order to configure the connection to the MongoDB. +If you use a dedicated MongoDB instead of the bundled container, configure the connection with +these environment variables: * `MONGO_DB_URI`: Connection string to MongoDB * `MONGO_DB_SSL_ENABLED`: Enabled SSL connection to MongoDB @@ -31,7 +31,7 @@ the following environment variables in order to configure the connection to the #### Passwordless authentication at MongoDB via AWS IAM -Starting with Ditto `3.6.0`, it is possible to [set up authentication with AWS IAM](https://www.mongodb.com/docs/atlas/security/aws-iam-authentication/). +Since Ditto `3.6.0`, you can [set up authentication with AWS IAM](https://www.mongodb.com/docs/atlas/security/aws-iam-authentication/). To enable this: 1. configure your Kubernetes serviceaccount with the role ARN via annotation `eks.amazonaws.com/role-arn` @@ -92,25 +92,23 @@ things: ### Ditto configuration -Each of Ditto's microservice has many options for configuration, e.g. timeouts, cache sizes, etc. +Each Ditto microservice has many configuration options (timeouts, cache sizes, etc.). -In order to have a look at all possible configuration options and what default values they have, here are the -configuration files of Ditto's microservices: +To review all available options and their defaults, see the configuration files: * Policies: [policies.conf](https://github.com/eclipse-ditto/ditto/blob/master/policies/service/src/main/resources/policies.conf) * Things: [things.conf](https://github.com/eclipse-ditto/ditto/blob/master/things/service/src/main/resources/things.conf) * Things-Search: [things-search.conf](https://github.com/eclipse-ditto/ditto/blob/master/thingsearch/service/src/main/resources/search.conf) * Connectivity: [connectivity.conf](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf) * Gateway: [gateway.conf](https://github.com/eclipse-ditto/ditto/blob/master/gateway/service/src/main/resources/gateway.conf) -Whenever you find the syntax `${?UPPER_CASE_ENV_NAME}` in the configuration files, you may overwrite the default value -by specifying that environment variable when running the container. +When you see `${?UPPER_CASE_ENV_NAME}` in the configuration files, you can override the default value +by setting that environment variable when running the container. -When no environment variable is defined in the config, you may change the default value anyway by specifying a -"System property" you pass to the Java process. +If no environment variable is defined in the config, you can still change the default by passing a +"System property" to the Java process. -The following example configures the devops password of the gateway-service started via docker-compose. In order -to supply additional configuration one has to add the variable in the corresponding `command` section of the -`docker-compose.yml` file. +The following example configures the devops password of the gateway-service started via docker-compose. Add the variable in the corresponding `command` section of the +`docker-compose.yml` file: ```yml ... @@ -124,13 +122,13 @@ the `-jar` option. ### Pre-authentication -HTTP API calls to Ditto may be authenticated with a reverse proxy (e.g. a nginx) which: +You can authenticate HTTP API calls to Ditto with a reverse proxy (e.g. nginx) that: * authenticates a user/subject * passes the authenticated username as HTTP header * ensures that this HTTP header can never be written by the end-user By default, `pre-authentication` is **disabled** in the Ditto [gateway](architecture-services-gateway.html) services. -It can however be enabled by configuring the environment variable `ENABLE_PRE_AUTHENTICATION` to the value `true`. +Enable it by setting the environment variable `ENABLE_PRE_AUTHENTICATION` to `true`. When it is enabled, the reverse proxy has to set the HTTP header `x-ditto-pre-authenticated`.
    The format of the "pre-authenticated" string is: `:`. The issuer defines which system authenticated @@ -139,7 +137,7 @@ the user and the subject contains e.g. the user-id or -name. This string must then be used in [policies](basic-policy.html#subjects) as "Subject ID". Example for a nginx "proxy" configuration: -``` +```text auth_basic "Authentication required"; auth_basic_user_file nginx.htpasswd; ... @@ -149,7 +147,7 @@ proxy_set_header x-ditto-pre-authenticated "nginx:${remote_user}"; ### OpenID Connect -The authentication provider must be added to the ditto-gateway configuration with unique configuration key +Add the authentication provider to the ditto-gateway configuration with a unique key (e.g. `myprovier` in the example below). Either `issuer` as single supported JWT `"iss"` claim or `issuers` (as a list of supported JWT `"iss"` claims) has to be @@ -173,7 +171,7 @@ Using `inject-claims-into-headers`, it e.g. is possible to add the email address header to a command so that e.g. in logging it can be determined which user caused a change. -``` +```hocon ditto.gateway.authentication { oauth { openid-connect-issuers = { @@ -240,7 +238,7 @@ it to Ditto as `Authorization` header. **If the chosen OIDC provider uses a self-signed certificate**, the certificate has to be retrieved and configured for the pekko-http ssl configuration. -``` +```hocon ssl-config { trustManager = { stores = [ @@ -286,7 +284,7 @@ above ([EncryptorAesGcm.generateAESKeyAsString()](https://github.com/eclipse-dit #### Fields config The fields to be encrypted are configurable as json pointers and the default ones are: -``` +```text /uri /credentials/key /sshTunnel/credentials/password @@ -514,7 +512,7 @@ be customized, and the ability to create new entities can be restricted. In the `ditto-entity-creation.conf`, you can re-configure the `entity-creation` section to suit your needs. The basic schema is: -``` +```hocon # restrict entity creation ditto.entity-creation { # this default entry allows every authenticated "auth-subject" to create any "resource-type" in any "namespace": @@ -562,7 +560,7 @@ An entry matches, when all the following conditions are met: This means, an existing entry, with all empty lists, will match. So the default configuration, allowing all access, can be as simple as: -``` +```hocon ditto.entity-creation { grant = [{}] } diff --git a/documentation/src/main/resources/pages/ditto/intro-digitaltwins.md b/documentation/src/main/resources/pages/ditto/intro-digitaltwins.md index 0673dcb64a9..529f64339f2 100644 --- a/documentation/src/main/resources/pages/ditto/intro-digitaltwins.md +++ b/documentation/src/main/resources/pages/ditto/intro-digitaltwins.md @@ -1,41 +1,68 @@ --- -title: Digital twins +title: Digital Twins Explained keywords: digital twin, digitaltwin, twin, administrationshell, asset tags: [getting_started] permalink: intro-digitaltwins.html --- -{% include callout.html content="**TL;DR**
    Digital twins are a pattern for simplifying IoT solution development." type="primary" %} +A digital twin is a virtual representation of a physical device or asset that stays synchronized +with the real world. -The problem with the term **digital twin** is that there are many different understandings of what it means. -Furthermore, the term was previously mostly used and coined by marketing. The term was/is missing a -technical foundation of what to expect from a framework for digital twins. +{% include callout.html content="**TL;DR**: A digital twin mirrors a physical device as a JSON data structure in the +cloud, giving your applications a single, always-available source of truth about that device." type="primary" %} -Eclipse Ditto provides such a framework for digital twins and this page describes how Ditto defines/sees digital twins -from a technical perspective. +## What is a digital twin? -## Digital twin from a technical perspective +Imagine you have a temperature sensor in a warehouse. The sensor connects to the internet and +reports its reading every 30 seconds. A digital twin for that sensor is a JSON object stored in +Ditto that always reflects the sensor's latest state: -For Eclipse Ditto the **digital twin** is a concept for abstracting a real world asset/device with -all capabilities and aspects including its digital representation. +```json +{ + "thingId": "com.example:warehouse-sensor-1", + "attributes": { + "location": "Warehouse B, Shelf 3" + }, + "features": { + "temperature": { + "properties": { + "value": 22.5, + "unit": "Celsius" + } + } + } +} +``` -A digital twin -* mirrors physical assets/devices -* acts as a "single source of truth" for a physical asset -* provides various aspects+services around devices -* keeps real and digital worlds in sync -* can be applied in both industrial and consumer-centric IoT scenarios +When the sensor sends a new reading, Ditto updates the twin. When your dashboard queries the twin, +it gets the latest value -- even if the sensor is temporarily offline. -A digital twin framework -* provides capabilities (APIs) to interact with digital twins -* ensures that access to twins can only be done by authorized parties -* allows to not only interact with single twins but also with populations of many of them -* integrates into other back-end infrastructure (like messaging systems, brokers) +## How Ditto defines digital twins + +From a technical perspective, a digital twin in Ditto: + +* **Mirrors a physical asset** -- stores the device's current and desired state as structured JSON +* **Acts as a single source of truth** -- applications read from and write to the twin instead of + talking directly to the device +* **Stays synchronized** -- updates flow from device to twin and from twin to device +* **Enforces access control** -- a [Policy](basic-policy.html) determines who can read or modify + each part of the twin + +Ditto as a digital twin framework: + +* Provides APIs (HTTP, WebSocket, and other messaging protocols) to interact with twins +* Ensures that only authorized parties can access twin data +* Supports working with individual twins or querying across large populations of twins +* Integrates with messaging systems and brokers for device connectivity ## Industrial context -In the IIoT the **digital twin** -metaphor is becoming a popular concept for tracking a produced product/good in its complete lifecycle. +In the IIoT, +digital twins track manufactured products and assets throughout their lifecycle. The concept is +closely related to the "Asset Administration Shell" used in Industry 4.0 scenarios. + +## Further reading -Another term often used in the IIoT in combination with digital twin is the "Asset Administration Shell" -("Verwaltungsschale" in german). +* [Hello World Tutorial](intro-hello-world.html) -- create your first digital twin +* [Data Model Overview](basic-overview.html) -- understand how Things, Features, and Policies fit + together diff --git a/documentation/src/main/resources/pages/ditto/intro-hello-world.md b/documentation/src/main/resources/pages/ditto/intro-hello-world.md index cec3fb68bee..0b9cb3b05cd 100644 --- a/documentation/src/main/resources/pages/ditto/intro-hello-world.md +++ b/documentation/src/main/resources/pages/ditto/intro-hello-world.md @@ -1,21 +1,26 @@ --- -title: Hello world +title: Hello World Tutorial tags: [getting_started] permalink: intro-hello-world.html --- -After [starting Ditto](installation-running.html), we have an HTTP and WebSocket API for your -[digital twins](intro-digitaltwins.html) at our hands. +This tutorial walks you through creating, querying, and updating your first digital twin using +Ditto's HTTP API. -## Example +{% include callout.html content="**TL;DR**: Create a Thing with a `POST` request, read it with `GET`, and update individual +properties with `PUT` -- all using standard HTTP." type="primary" %} -Assume we want to create a digital twin for a car. The twin should hold static metadata and dynamic state data. -The state data should change as often as its real world counterpart does. +## Prerequisites -Those static and dynamic types of data are mapped in the Ditto model to "attributes" (for static metadata), "features" -(for dynamic state data) and "definition" (to link a model the thing follows, e.g. a -[WoT (Web of Things)](basic-wot-integration.html) "Thing Model"). -A JSON representation of some metadata and state data could for example look like this: +* A running Ditto instance (see [Installation & Running](installation-running.html)) +* [cURL](https://github.com/curl/curl) or another HTTP client +* The default credentials `ditto:ditto` set up by the Docker deployment's nginx + (see [Docker deployment README](https://github.com/eclipse-ditto/ditto/blob/master/deployment/docker/README.md)) + +## What a complete Thing looks like + +Before diving into the steps, here is a fully modeled floor lamp Thing with 7 features. This is +what you are building toward: ```json { @@ -35,11 +40,7 @@ A JSON representation of some metadata and state data could for example look lik ], "properties": { "dimmer-level": 0, - "color": { - "r": 0, - "g": 0, - "b": 0 - }, + "color": { "r": 0, "g": 0, "b": 0 }, "on": false } }, @@ -51,11 +52,7 @@ A JSON representation of some metadata and state data could for example look lik ], "properties": { "dimmer-level": 0, - "color": { - "r": 0, - "g": 0, - "b": 0 - }, + "color": { "r": 0, "g": 0, "b": 0 }, "on": false } }, @@ -67,11 +64,7 @@ A JSON representation of some metadata and state data could for example look lik ], "properties": { "dimmer-level": 0, - "color": { - "r": 0, - "g": 0, - "b": 0 - }, + "color": { "r": 0, "g": 0, "b": 0 }, "on": false } }, @@ -103,11 +96,7 @@ A JSON representation of some metadata and state data could for example look lik "https://eclipse-ditto.github.io/ditto-examples/wot/models/switchable-1.0.0.tm.jsonld" ], "properties": { - "color": { - "r": 0, - "g": 0, - "b": 0 - }, + "color": { "r": 0, "g": 0, "b": 0 }, "on": false } } @@ -115,74 +104,115 @@ A JSON representation of some metadata and state data could for example look lik } ``` -Background: Ditto only knows about "attributes", "features" and the "definition". - -Inside "attributes" (the metadata) we can add as much JSON keys as we like with any JSON value we need. +A Thing has three top-level data sections: -Inside "features" (the state data) we can add as many features as we like - but each feature needs to have -a "properties" JSON object. Inside that JSON object we can add as much JSON keys as we like with any JSON value we need. +* **attributes**: static metadata (manufacturer, serial number) -- any JSON structure +* **features**: dynamic state data -- each feature has a `properties` object and optionally a `definition` + linking to a [WoT Thing Model](basic-wot-integration.html) +* **definition**: model reference -- a single string linking to a Thing Model describing the Thing's + capabilities -Inside "definition" we can add one JSON string value. +## Step 1: Create a Thing -## Creating your first Thing - -We create a Thing for the example from above by using [cURL](https://github.com/curl/curl). Basic authentication will use the credentials of a user "ditto". -Those credentials have been created by default in the [nginx](https://github.com/nginx/nginx) started via "docker". -(See [ditto/deployment/docker/README.md](https://github.com/eclipse-ditto/ditto/blob/master/deployment/docker/README.md)) +Send a PUT request to create a new Thing with a specific ID. This example creates a floor lamp +with metadata in `attributes` and a model reference in `definition`: ```bash -curl -u ditto:ditto -X POST -H 'Content-Type: application/json' -d '{ - "definition": "https://eclipse-ditto.github.io/ditto-examples/wot/models/floor-lamp-1.0.0.tm.jsonld", - "attributes": { - "manufacturer": "ACME", - "VIN": "0815666337" - } - }' 'http://localhost:8080/api/2/things' +curl -u ditto:ditto -X PUT -H 'Content-Type: application/json' -d '{ + "definition": "https://eclipse-ditto.github.io/ditto-examples/wot/models/floor-lamp-1.0.0.tm.jsonld", + "attributes": { + "manufacturer": "ACME", + "serialNo": "0815666337" + } +}' 'http://localhost:8080/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815' ``` -The result is a digital twin in Thing notation. The Thing ID is generated when using the `POST` HTTP verb. -An ID must always contain a namespace before the `:`. That way Things are easier to organize. +**What happens:** Ditto creates the Thing and returns `201 Created`. The ID +`io.eclipseprojects.ditto:floor-lamp-0815` contains a namespace (`io.eclipseprojects.ditto`) +before the `:` to help organize your Things. -## Querying an existing Thing +{% include note.html content="You can also use `POST` to `/api/2/things` without specifying an ID -- Ditto will auto-generate one." %} -By creating the digital twin as a Thing with the specified JSON format, Ditto implicitly provides an API for -our Thing. +## Step 2: Retrieve the Thing -For Things we know the ID of, we can simply query them by their ID: +Query the Thing by its ID: ```bash -curl -u ditto:ditto -X GET 'http://localhost:8080/api/2/things/org.eclipse.ditto:fancy-car' +curl -u ditto:ditto -X GET \ + 'http://localhost:8080/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815' | jq + +# if you have python installed, that's an alternative for pretty-printing: +curl -u ditto:ditto -X GET \ + 'http://localhost:8080/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815' | python -m json.tool +``` + +**What happens:** Ditto returns the full JSON representation of your Thing, including its +`thingId`, `policyId`, `definition`, `attributes`, and `features`. + +## Step 3: Add a Feature with state data -# if you have jq installed, that's how to get a prettier response: -curl -u ditto:ditto -X GET 'http://localhost:8080/api/2/things/org.eclipse.ditto:fancy-car' | jq +Add a feature to represent the lamp's first spot light. Features hold dynamic state data like +sensor readings or device configuration: -# if you have python installed, that's how to get a prettier response: -curl -u ditto:ditto -X GET 'http://localhost:8080/api/2/things/org.eclipse.ditto:fancy-car' | python -m json.tool +```bash +curl -u ditto:ditto -X PUT -H 'Content-Type: application/json' -d '{ + "properties": { + "on": false, + "dimmer-level": 0, + "color": { "r": 0, "g": 0, "b": 0 } + } +}' 'http://localhost:8080/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815/features/Spot1' ``` -## Querying one specific state value +**What happens:** Ditto adds a `Spot1` feature to your Thing with the specified properties. -The created API for our Thing also provides HTTP endpoints for each attribute and feature property. +## Step 4: Read a single property -That way we can for example just retrieve the `cur_speed` of our fancy car: +Ditto exposes every attribute and feature property as its own HTTP endpoint. Retrieve the +current on/off state of `Spot1`: ```bash -curl -u ditto:ditto -X GET 'http://localhost:8080/api/2/things/org.eclipse.ditto:fancy-car/features/transmission/properties/cur_speed' +curl -u ditto:ditto -X GET \ + 'http://localhost:8080/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815/features/Spot1/properties/on' ``` -## Updating one specific state value +**What happens:** Ditto returns the value `false` (or whatever the current value is). + +## Step 5: Update a single property -We can just as easy use the HTTP API to update one attribute or feature property, e.g. update the `cur_speed` to `77`: +Turn on the lamp spot by updating its `on` property: ```bash -curl -u ditto:ditto -X PUT -H 'Content-Type: application/json' -d '77' 'http://localhost:8080/api/2/things/org.eclipse.ditto:fancy-car/features/transmission/properties/cur_speed' +curl -u ditto:ditto -X PUT -H 'Content-Type: application/json' -d 'true' \ + 'http://localhost:8080/api/2/things/io.eclipseprojects.ditto:floor-lamp-0815/features/Spot1/properties/on' ``` -## Searching for all Things +**What happens:** Ditto updates the property and returns `204 No Content` on success. -When we lost the overview which Things we have already created, we can use the `search` HTTP endpoint, -e.g. searching all Things with the same `manufacturer` named `"ACME"`: +## Step 6: Search for Things + +Find all Things from a specific manufacturer using Ditto's search API: ```bash -curl -u ditto:ditto -X GET 'http://localhost:8080/api/2/search/things?filter=eq(attributes/manufacturer,"ACME")' +curl -u ditto:ditto -X GET \ + 'http://localhost:8080/api/2/search/things?filter=eq(attributes/manufacturer,"ACME")' ``` + +**What happens:** Ditto searches across all Things you have access to and returns those matching +the filter. + +## What you learned + +In this tutorial you: + +1. Created a digital twin (Thing) via the HTTP API +2. Retrieved the full Thing and individual properties +3. Added a Feature with state data +4. Updated a single property value +5. Searched for Things by attribute values + +## Further reading + +* [Data Model Overview](basic-overview.html) -- understand Things, Features, and Attributes in detail +* [Policies](basic-policy.html) -- control who can read and write your Things +* [Messages](basic-messages.html) -- send commands to actual devices through their twins diff --git a/documentation/src/main/resources/pages/ditto/intro-overview.md b/documentation/src/main/resources/pages/ditto/intro-overview.md index 6758b570f3f..705b3810e83 100644 --- a/documentation/src/main/resources/pages/ditto/intro-overview.md +++ b/documentation/src/main/resources/pages/ditto/intro-overview.md @@ -1,58 +1,51 @@ --- -title: Eclipse Ditto™ documentation overview +title: What is Eclipse Ditto? keywords: purpose, about, motivation, digital twin, digitaltwin, twin tags: [getting_started] permalink: intro-overview.html --- +Eclipse Ditto is an open-source framework for building [digital twins](intro-digitaltwins.html) in the +IoT. +It lets you represent physical devices -- sensors, machines, vehicles, and more -- as JSON-based +"Things" that your applications interact with through standard web APIs. + +{% include callout.html content="**TL;DR**: Ditto provides a ready-to-use backend for managing digital twins, so you can +interact with devices through HTTP, WebSocket, and messaging APIs without building a custom IoT backend." type="primary" %} ## What is it? -Eclipse Ditto™ is a technology in the IoT -implementing a software pattern called “**[digital twins](intro-digitaltwins.html)**”.
    -A digital twin is a virtual, cloud based, representation of his real world counterpart -(real world “Things”, e.g. devices like sensors, smart heating, connected cars, smart grids, EV charging stations, …). +Ditto mirrors real-world devices as virtual "Things" in the cloud. Each Thing holds the device's +last-known state, its metadata, and a policy that controls who can read or write its data. -The technology mirrors potentially millions and billions of digital twins residing in the digital world -with physical “**Things**”. This simplifies developing IoT solutions for software developers as they do not need -to know how or where exactly the physical “Things” are connected. +Your applications never need to know *how* a device connects or *which protocol* it speaks. +You work with a Thing the same way you work with any web resource: through REST-style APIs. -With Ditto a thing can just be used as any other web service via its digital twin. +## What is it not? +Ditto is **not** an end-to-end IoT platform. It does not: -## What is it not? +* Run software on gateways or edge devices +* Define or implement a device communication protocol +* Prescribe the data structure a device must use -Ditto is not another fully-fledged IoT platform. It does not provide software running on IoT gateways, and it does not -define or implement an IoT protocol in order to communicate with devices. +Ditto focuses on the **backend layer**. It assumes your devices are already connected to the internet +(for example via [Eclipse Hono](https://www.eclipse.org/hono/)) and provides web APIs so your +applications can work with those devices as digital twins. -Its focus lies on back end scenarios by providing web APIs in order to simplify working with already connected (e.g. -via [Eclipse Hono](https://www.eclipse.org/hono/)) devices and “Things” from customer apps or other back end software. +## When to use it? -It also does not specify which data or which structure a “Thing” in the IoT has to provide. +Consider a typical IoT solution. You have hardware (sensors, actuators) and software (mobile apps, +dashboards, backend services). The backend is responsible for: +* **Providing an API** that abstracts away hardware details +* **Routing requests** between devices and applications +* **Enforcing authorization** so that each user or service accesses only the data it should +* **Caching device state** so applications can read data even when a device is offline +* **Notifying interested parties** when device state changes -## When to use it? +Ditto handles all of these responsibilities out of the box. - {% include callout.html content="**TL;DR**
    Use it in order to get a fully-fledged, authorization aware API - (HTTP, WebSocket and other messaging protocols) for interacting with your digital twins and all aspects around them." type="primary" %} - -Imagine you are building an IoT solution. And let's assume that you use both hardware (e.g. sensors or actuators) and -software (e.g. a mobile or web app) in order to solve your customer's problem. - -In such a scenario you have several places where to implement software: -* on or near the hardware, e.g. on an Arduino using `C/C++` or on an Raspberry PI using `Python`, -* optionally on a gateway establishing the Internet connectivity (e.g. based on [Eclipse Kura](https://www.eclipse.org/kura/)), -* in the mobile or web app using `Java`, `Javascript`, `Swift` etc., -* in the “back end” fulfilling several responsibilities like - * providing an API abstracting from the hardware, - * routing requests between hardware and customer apps, - * ensuring only authorized access, - * persisting last reported state of hardware as cache and for providing the data when hardware is currently not connected, - * notifying interested parties (e.g. other back end services) about changes, - * … - -Ditto focuses on solving the responsibilities a typical “back end” has in such scenarios. - - {% include callout.html content="Its goal is to free IoT solutions from the need of implementing and operating a - custom back end. Instead by using Eclipse Ditto they can focus on business requirements, on connecting devices to - the cloud/back end and on implementing business applications." type="info" %} +{% include callout.html content="Ditto's goal is to eliminate the need to build and operate a custom IoT backend. +You focus on connecting your devices and building your business applications -- Ditto handles +everything in between." type="info" %} diff --git a/documentation/src/main/resources/pages/ditto/operating-authentication.md b/documentation/src/main/resources/pages/ditto/operating-authentication.md new file mode 100644 index 00000000000..f08488f8382 --- /dev/null +++ b/documentation/src/main/resources/pages/ditto/operating-authentication.md @@ -0,0 +1,141 @@ +--- +title: Operating - Authentication +tags: [installation] +keywords: operating, authentication, pre-authentication, openid connect, oidc, oauth, jwt +permalink: operating-authentication.html +--- + +You authenticate HTTP API calls to Ditto through pre-authentication with a reverse proxy or through OpenID Connect (OIDC) providers. + +{% include callout.html content="**TL;DR**: Use pre-authentication when a reverse proxy handles user verification and passes the identity via HTTP header. Use OpenID Connect when you need JWT-based authentication from an OIDC provider like Keycloak." type="primary" %} + +## Overview + +Ditto supports two authentication mechanisms at the [gateway](architecture-services-gateway.html) layer: + +* **Pre-authentication**: A reverse proxy authenticates users and passes the identity to Ditto via an HTTP header. +* **OpenID Connect**: Ditto validates JWT tokens issued by configured OIDC providers. + +## Pre-authentication + +### How it works + +A reverse proxy (such as nginx) sits in front of Ditto and: + +1. Authenticates the user or subject. +2. Passes the authenticated username as an HTTP header. +3. Ensures that end users cannot set this header directly. + +### Setup + +Pre-authentication is **disabled** by default. Enable it by setting the environment variable `ENABLE_PRE_AUTHENTICATION` to `true`. + +When enabled, the reverse proxy must set the HTTP header `x-ditto-pre-authenticated` with the format: + +```text +: +``` + +The `issuer` identifies the authenticating system, and the `subject` contains the user ID or username. Use this string as the "Subject ID" in [policies](basic-policy.html#subjects). + +### Example: nginx configuration + +```nginx +auth_basic "Authentication required"; +auth_basic_user_file nginx.htpasswd; +... +proxy_set_header x-ditto-pre-authenticated "nginx:${remote_user}"; +``` + +## OpenID Connect + +### How it works + +You register an OIDC provider in the gateway configuration. Ditto validates incoming JWT tokens against the provider's public keys and extracts authorization subjects from configurable JWT claims. + +Ditto expects the headers `Authorization: Bearer ` and `Content-Type: application/json` on authenticated requests. + +**You must obtain the JWT token before calling Ditto.** Ditto does not handle token issuance. Use an OIDC provider like [Keycloak](https://www.keycloak.org/) or a project like [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy) to manage the token lifecycle. + +### Configuration + +Add your provider to the gateway configuration with a unique key: + +```hocon +ditto.gateway.authentication { + oauth { + openid-connect-issuers = { + myprovider = { + issuer = "localhost:9000" + auth-subjects = [ + "{%raw%}{{ jwt:sub }}{%endraw%}", + "{%raw%}{{ jwt:sub }}/{{ jwt:scp }}{%endraw%}", + "{%raw%}{{ jwt:sub }}/{{ jwt:scp }}@{{ jwt:client_id }}{%endraw%}", + "{%raw%}{{ jwt:sub }}/{{ jwt:scp }}@{{ jwt:non_existing }}{%endraw%}", + "{%raw%}{{ jwt:roles/support }}{%endraw%}" + ] + inject-claims-into-headers = { + user-email = "{%raw%}{{ jwt:email }}{%endraw%}" + user-name = "{%raw%}{{ jwt:name }}{%endraw%}" + } + } + } + } +} +``` + +**Key configuration options:** + +* `issuer`: A single JWT `"iss"` claim value (without `http://` or `https://` prefix). +* `issuers`: A list of supported `"iss"` claim values. If set, this takes priority over `issuer`. +* `auth-subjects`: A list of placeholder templates evaluated against incoming JWTs. Each entry generates an authorization subject. Entries with unresolvable placeholders are ignored. Defaults to `{%raw%}{{ jwt:sub }}{%endraw%}` when not provided. +* `inject-claims-into-headers` (since Ditto 3.8.0): A map of HTTP header names to JWT claim placeholders. Resolved values are added as custom headers to commands and preserved through forwarding. + +See [OpenID Connect configuration placeholders](basic-placeholders.html#scope-openid-connect-configuration) for the full placeholder syntax. + +{% include note.html content="The issuer **must not** include the `http://` or `https://` prefix as this is added + based on the configuration value of `ditto.gateway.authentication.oauth.protocol`." %} + +### Configuration via system properties + +```bash +-Dditto.gateway.authentication.oauth.openid-connect-issuers.myprovider.issuer=localhost:9000 +-Dditto.gateway.authentication.oauth.openid-connect-issuers.myprovider.auth-subjects.0='{%raw%}{{ jwt:sub }}/{{ jwt:scp }}{%endraw%}' +``` + +### Subject format in policies + +The configured subject-issuer prefixes each `auth-subject` value in policies: + +```json +{ + "subjects": { + ":": { + "type": "generated" + }, + ":": { + "type": "generated" + } + } +} +``` + +### Self-signed certificates + +If your OIDC provider uses a self-signed certificate, configure it for the Pekko HTTP SSL settings: + +```hocon +ssl-config { + trustManager = { + stores = [ + { type = "PEM", path = "/path/to/cert/globalsign.crt" } + ] + } +} +``` + +## Further reading + +* [Policies](basic-policy.html) +* [OpenID Connect placeholders](basic-placeholders.html#scope-openid-connect-configuration) +* [Operating - Configuration](operating-configuration.html) diff --git a/documentation/src/main/resources/pages/ditto/operating-configuration.md b/documentation/src/main/resources/pages/ditto/operating-configuration.md new file mode 100644 index 00000000000..1a0179f6a39 --- /dev/null +++ b/documentation/src/main/resources/pages/ditto/operating-configuration.md @@ -0,0 +1,666 @@ +--- +title: Operating - Configuration +tags: [installation] +keywords: operating, configuration, environment variables, system properties, config files, rate limiting, entity creation +permalink: operating-configuration.html +--- + +You configure Ditto services through config files, environment variables, or Java system properties. + +{% include callout.html content="**TL;DR**: Override any Ditto config value by setting an environment variable (when the config uses `${?ENV_NAME}` syntax) or by passing a Java system property (`-Dkey=value`) to the service process." type="primary" %} + +## Overview + +Each Ditto microservice ships with a default [HOCON](https://github.com/lightbend/config/blob/main/HOCON.md) configuration file. You can customize behavior without modifying source code by using environment variables, Java system properties, or extension config files. + +## How it works + +### Config file structure + +Each microservice has its own configuration file with sensible defaults: + +* Policies: [policies.conf](https://github.com/eclipse-ditto/ditto/blob/master/policies/service/src/main/resources/policies.conf) +* Things: [things.conf](https://github.com/eclipse-ditto/ditto/blob/master/things/service/src/main/resources/things.conf) +* Things-Search: [things-search.conf](https://github.com/eclipse-ditto/ditto/blob/master/thingsearch/service/src/main/resources/search.conf) +* Connectivity: [connectivity.conf](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf) +* Gateway: [gateway.conf](https://github.com/eclipse-ditto/ditto/blob/master/gateway/service/src/main/resources/gateway.conf) + +### Environment variables + +When you find the syntax `${?UPPER_CASE_ENV_NAME}` in a config file, you can override that value by setting the corresponding environment variable in the container. + +### Java system properties + +When no environment variable is defined for a config option, you can still change the default by passing a Java system property to the process. + +The following example sets the DevOps password for the gateway service in a `docker-compose.yml` file: + +```yaml +environment: + - JAVA_TOOL_OPTIONS=-Dditto.gateway.authentication.devops.password=foobar +``` + +The microservice executable is called `starter.jar`. Place all system properties before the `-jar` option. + +## Configuration topics + +### Restricting entity creation + +By default, Ditto allows any authenticated user to create policies or things in any namespace. You can restrict this by editing the `ditto-entity-creation.conf` file. + +The basic schema uses `grant` and `revoke` lists: + +```hocon +ditto.entity-creation { + grant = [ + { + resource-types = [] + namespaces = [] + auth-subjects = [] + thing-definitions = [] + } + ] + revoke = [] +} +``` + +The enforcement logic works as follows: + +1. Find a matching entry in the `grant` list. +2. Check that no matching entry exists in the `revoke` list. +3. Accept the request if both conditions pass; otherwise deny it. + +An entry matches when **all** of these conditions are true: + +* The `resource-types` list is empty or contains the requested resource type (`policy` or `thing`). +* The `namespaces` list is empty or contains a wildcard matching the requested namespace (`*` matches any number of characters, `?` matches exactly one). +* The `auth-subjects` list is empty or contains at least one matching wildcard for the request's auth subjects. +* For `thing` resources only: the `thing-definitions` list is empty or contains a matching wildcard. + +An entry with all empty lists matches everything. So the simplest "allow all" configuration is: + +```hocon +ditto.entity-creation { + grant = [{}] +} +``` + +To restrict entity creation to specific subjects via system properties: + +```bash +-Dditto.entity-creation.grant.0.auth-subjects.0=pre:admin +-Dditto.entity-creation.grant.0.auth-subjects.1=integration:some-connection +``` + +Configure these properties on both the "things" and "policies" services. + +### Encrypting sensitive connection data + +Since Ditto 3.1.0, you can encrypt sensitive fields in [connections](basic-connections.html) before they reach the database. This encryption is transparent -- retrieval endpoints return decrypted data automatically. + +Ditto uses 256-bit AES with AES/GCM/NoPadding. You can generate a key with: + +```bash +openssl rand -base64 32 +``` + +or using the Java standard library: + +```java +javax.crypto.KeyGenerator keyGen = KeyGenerator.getInstance("AES"); +keyGen.init(256); +javax.crypto.SecretKey aes256SymmetricKey = keyGen.generateKey(); +``` + +or use the convenience method [EncryptorAesGcm.generateAESKeyAsString()](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/java/org/eclipse/ditto/connectivity/service/util/EncryptorAesGcm.java#L100). + +The key must be 256-bit, [Base64-encoded with URL-safe alphabet](https://www.rfc-editor.org/rfc/rfc4648#section-5) using UTF-8. + +The default fields that get encrypted are: + +* `/uri` +* `/credentials/key` +* `/sshTunnel/credentials/password` +* `/sshTunnel/credentials/privateKey` +* `/credentials/parameters/accessKey` +* `/credentials/parameters/secretKey` +* `/credentials/parameters/sharedKey` +* `/credentials/clientSecret` + +Only string values are supported. URI values receive special treatment -- only the password portion of the user info is encrypted. + +Find the full encryption configuration in [connectivity.conf](https://github.com/eclipse-ditto/ditto/blob/master/connectivity/service/src/main/resources/connectivity.conf) at the `ditto.connectivity.connection.encryption` section. + +{% include note.html content="If you disable encryption later, keep the symmetric key in the configuration. Without it, previously encrypted values cannot be decrypted, and you would need to manually re-enter the encrypted connection fields." %} + +#### Encryption key rotation + +Since Ditto 3.9.0, you can rotate encryption keys without downtime or data loss using a dual-key configuration +and a migration command. + +##### Dual-key configuration + +The encryption configuration supports both a current key and an optional old key for fallback decryption: + +```hocon +ditto.connectivity.connection.encryption { + encryption-enabled = true + symmetrical-key = "YOUR_NEW_KEY_HERE" # Current key for encrypting new data + old-symmetrical-key = "YOUR_OLD_KEY_HERE" # Optional fallback key for decrypting old data + json-pointers = [...] +} +``` + +**Behavior:** +- **Encryption:** Always uses `symmetrical-key` for encrypting new data +- **Decryption:** Tries `symmetrical-key` first, falls back to `old-symmetrical-key` if decryption fails +- **Migration:** Explicit DevOps command re-encrypts existing data from old key to new key + +**Migration Decision Logic:** + +The migration command automatically detects the intended workflow based on configuration: + +- **Encryption enabled + both keys set** → Key rotation (decrypt with old, encrypt with new) +- **Encryption enabled + only current key** → Error (nothing to migrate) +- **Encryption disabled + old key set** → Disable workflow (decrypt with old, write plaintext) +- **Encryption disabled + no keys** → Error (cannot migrate) + +##### Key rotation workflow + +To rotate an encryption key: + +1. **Generate a new encryption key** using the methods described above + +2. **Update configuration** with both keys: + ```hocon + ditto.connectivity.connection.encryption { + encryption-enabled = true + symmetrical-key = "NEW_KEY" # New key + old-symmetrical-key = "OLD_KEY" # Current key becomes old key + } + ``` + +3. **Restart connectivity service** to load the new configuration + +4. **Run dry-run migration** to verify affected documents: + ```bash + curl -X POST http://localhost:8080/devops/piggyback/connectivity \ + -u devops:devopsPw1! \ + -H 'Content-Type: application/json' \ + -d '{ + "targetActorSelection": "/user/connectivityRoot/encryptionMigration", + "headers": { + "aggregate": false + }, + "piggybackCommand": { + "type": "connectivity.commands:migrateEncryption", + "dryRun": true, + "resume": false + } + }' + ``` + +5. **Start actual migration** to re-encrypt all persisted data: + ```bash + curl -X POST http://localhost:8080/devops/piggyback/connectivity \ + -u devops:devopsPw1! \ + -H 'Content-Type: application/json' \ + -d '{ + "targetActorSelection": "/user/connectivityRoot/encryptionMigration", + "headers": { + "aggregate": false + }, + "piggybackCommand": { + "type": "connectivity.commands:migrateEncryption", + "dryRun": false, + "resume": false + } + }' + ``` + +6. **Monitor migration progress**: + ```bash + curl -X POST http://localhost:8080/devops/piggyback/connectivity \ + -u devops:devopsPw1! \ + -H 'Content-Type: application/json' \ + -d '{ + "targetActorSelection": "/user/connectivityRoot/encryptionMigration", + "headers": { + "aggregate": false + }, + "piggybackCommand": { + "type": "connectivity.commands:migrateEncryptionStatus" + } + }' + ``` + +7. **After successful migration**, remove the old key from configuration and restart the service + +**Additional migration commands:** + +- **Abort running migration:** + ```bash + curl -X POST http://localhost:8080/devops/piggyback/connectivity \ + -u devops:devopsPw1! \ + -H 'Content-Type: application/json' \ + -d '{ + "targetActorSelection": "/user/connectivityRoot/encryptionMigration", + "headers": { + "aggregate": false + }, + "piggybackCommand": { + "type": "connectivity.commands:migrateEncryptionAbort" + } + }' + ``` + +- **Resume aborted migration:** + ```bash + curl -X POST http://localhost:8080/devops/piggyback/connectivity \ + -u devops:devopsPw1! \ + -H 'Content-Type: application/json' \ + -d '{ + "targetActorSelection": "/user/connectivityRoot/encryptionMigration", + "headers": { + "aggregate": false + }, + "piggybackCommand": { + "type": "connectivity.commands:migrateEncryption", + "dryRun": false, + "resume": true + } + }' + ``` + + {% include note.html content="If the previous migration already completed, was never started, or only ran as a dry run (which does not persist progress), the resume command returns `200 OK` with `phase: \"already_completed\"` instead of starting a new migration. This makes resume safe to call idempotently." %} + +**Migration details:** +- The migration processes both connection snapshots and journal events in MongoDB +- Progress is persisted to allow resuming after abort or service restart +- Migration runs in batches to avoid overwhelming the database +- The batch size can be configured via `ditto.connectivity.connection.encryption.migration.batch-size` +- Migration is throttled to prevent database overload (default: 200 documents/minute) +- Throttling rate can be configured via `ditto.connectivity.connection.encryption.migration.max-documents-per-minute` +- Set throttling to 0 to disable (not recommended for production) + +##### Disabling encryption + +To disable encryption while preserving access to already encrypted data: + +1. **Update configuration** with encryption disabled but old key present: + ```hocon + ditto.connectivity.connection.encryption { + encryption-enabled = false + symmetrical-key = "" # Empty - no new encryption + old-symmetrical-key = "YOUR_CURRENT_KEY" # Keep for decryption + } + ``` + +2. **Restart connectivity service** + +3. **Run migration** to decrypt all existing encrypted data: + ```bash + curl -X POST http://localhost:8080/devops/piggyback/connectivity \ + -u devops:devopsPw1! \ + -H 'Content-Type: application/json' \ + -d '{ + "targetActorSelection": "/user/connectivityRoot/encryptionMigration", + "headers": { + "aggregate": false + }, + "piggybackCommand": { + "type": "connectivity.commands:migrateEncryption", + "dryRun": false, + "resume": false + } + }' + ``` + +4. **After migration completes**, remove the old key from configuration and restart + +### Rate limiting + +Since Ditto 2.4.0, [connections](basic-connections.html) and [WebSockets](httpapi-protocol-bindings-websocket.html) are not artificially throttled when consuming messages by default. You can enable per-connection or per-WebSocket throttling through the `throttling` sections in the service configuration files. + +### Pre-defined extra fields + +Starting with Ditto 3.7.0, you can statically configure [enrichment of `extraFields`](basic-enrichment.html) in the "things" service configuration. This avoids an internal roundtrip from edge services to the things service for each event or message. + +Configure pre-defined extra fields for events and messages independently: + +```hocon +ditto { + things { + thing { + event { + pre-defined-extra-fields = [ + { + namespaces = [] + condition = "exists(definition)" + extra-fields = ["definition"] + }, + { + namespaces = ["org.eclipse.ditto.lamps"] + extra-fields = [ + "attributes/manufacturer", + "attributes/serial" + ] + } + ] + } + + message { + pre-defined-extra-fields = [ + { + namespaces = [] + condition = "exists(definition)" + extra-fields = [ + "definition" + ] + } + ] + } + } + } +} +``` + +Configure pre-defined extra fields for `event` and `message` independently. Each entry supports: +* `namespaces`: restrict to specific namespaces (empty means all; supports `*` and `?` wildcards) +* `condition`: an [RQL condition](basic-rql.html) to check before adding extra fields +* `extra-fields`: list of JSON pointers to include proactively + +### Limiting indexed fields + +Since Ditto 3.5.0, you can control which thing fields get indexed in the search database per namespace pattern. This reduces search database load when you only search on a few fields. + +```hocon +ditto { + caching-signal-enrichment-facade-provider = org.eclipse.ditto.thingsearch.service.persistence.write.streaming.SearchIndexingSignalEnrichmentFacadeProvider + search { + namespace-indexed-fields = [ + { + namespace-pattern = "org.eclipse.test" + indexed-fields = [ + "attributes", + "features/info/properties" + ] + } + ] + } +} +``` + +Ditto matches the thing's namespace against the **first** matching `namespace-pattern`, so order your patterns from most specific to least specific. Ditto automatically adds system-level fields it needs to operate. + +To configure via system properties: + +```shell +-Dditto.search.namespace-indexed-fields.0.namespace-pattern=org.eclipse.test +-Dditto.search.namespace-indexed-fields.0.indexed-fields.0=attributes +-Dditto.search.namespace-indexed-fields.0.indexed-fields.1=features/info/properties +-Dditto.search.namespace-indexed-fields.0.indexed-fields.2=features/info/other +-Dditto.search.namespace-indexed-fields.1.namespace-pattern=org.eclipse* +-Dditto.search.namespace-indexed-fields.1.indexed-fields.0=attributes +-Dditto.search.namespace-indexed-fields.1.indexed-fields.1=features/info +``` + +### Configuring additional search indexes + +Since Ditto 3.9.0, you can define custom MongoDB indexes for the search collection to optimize specific query patterns. + +```hocon +ditto { + search { + index-initialization { + custom-indexes { + my_custom_idx { + fields = [ + { name = "t.attributes/region" } + { name = "t.attributes/timestamp", direction = "DESC" } + ] + } + } + } + } +} +``` + +Field naming conventions for custom indexes: + +| Field | Path | +|-------|------| +| Namespace | `_namespace` | +| Thing ID | `_id` | +| Policy ID | `t.policyId` | +| Attributes | `t.attributes.` | +| Feature properties | `t.features..properties.` | +| Last modified | `_modified` | +| Created | `_created` | + +Custom indexes activate automatically when defined, and Ditto drops them if you remove them from configuration. + +To configure via system properties: + +```shell +-Dditto.search.index-initialization.custom-indexes.my_custom_idx.fields.0.name=t.attributes/region +-Dditto.search.index-initialization.custom-indexes.my_custom_idx.fields.1.name=t.attributes/timestamp +-Dditto.search.index-initialization.custom-indexes.my_custom_idx.fields.1.direction=DESC +``` + +When deploying via Helm, configure in `values.yaml`: + +```yaml +thingsSearch: + config: + indexInitialization: + enabled: true + customIndexes: + my_custom_idx: + fields: + - name: "t.attributes/region" + direction: "ASC" + - name: "t.attributes/timestamp" + direction: "DESC" +``` + +### Merge operations configuration + +Starting with Ditto 3.8.0, the Things service supports configuration for merge operations with patch conditions. Set `MERGE_REMOVE_EMPTY_OBJECTS_AFTER_PATCH_CONDITION_FILTERING` to `true` to remove empty JSON objects that result from patch condition filtering: + +```hocon +ditto { + things { + thing { + merge { + remove-empty-objects-after-patch-condition-filtering = true + remove-empty-objects-after-patch-condition-filtering = ${?MERGE_REMOVE_EMPTY_OBJECTS_AFTER_PATCH_CONDITION_FILTERING} + } + } + } +} +``` + +When deploying via Helm: + +```yaml +things: + config: + merge: + removeEmptyObjectsAfterPatchConditionFiltering: true +``` + +Or as a Kubernetes environment variable: + +```yaml +env: +- name: MERGE_REMOVE_EMPTY_OBJECTS_AFTER_PATCH_CONDITION_FILTERING + value: "true" +``` + +### Gateway namespace access control + +Since Ditto *3.9.0*, the Ditto **gateway** service supports restricting which namespaces a client can access based on +the JWT claims or HTTP headers present in the request. This provides a cheap enforcement layer at the API gateway level, +before policy-based access control is evaluated. + +Namespace access control is configured via `ditto.gateway.authentication.namespace-access` in +[gateway.conf](https://github.com/eclipse/ditto/blob/master/gateway/service/src/main/resources/gateway.conf). + +#### How it works + +A list of **rules** is defined. Each rule can specify: +- **`conditions`** (AND semantics): a list of placeholder expressions that must all evaluate to a non-empty value for + the rule to apply. Placeholders: `{%raw%}{{ jwt:claim }}{%endraw%}` for JWT claims, `{%raw%}{{ header:name }}{%endraw%}` for HTTP headers. + Functions `fn:filter` and `fn:default` can be used (see below). +- **`resource-types`**: list of resource types this rule applies to (`"thing"`, `"policy"`). + An empty list means the rule applies to all resource types. +- **`allowed-namespaces`**: list of exact namespace names or wildcard patterns (`*` = any chars, `?` = single char). + An empty list means all namespaces are allowed (unless blocked). +- **`blocked-namespaces`**: list of exact namespace names or wildcard patterns that are explicitly blocked (takes precedence over allowed). + +Multiple rules are evaluated with **OR semantics**: a namespace is accessible if it is allowed by *any* matching rule. +**Fail-closed**: if namespace-access rules are configured but none of them match the current request (i.e. no rule's +conditions are satisfied), access is **denied**. If no rules are configured at all, access is allowed (backward compatible). +This means a request from an unrecognized issuer or with unexpected headers will be denied rather than silently granted full access. + +#### Search behavior + +For `GET /search/things` requests without an explicit `namespaces` parameter, Ditto automatically injects the +allowed namespaces from the applicable rule. If only wildcard patterns are configured (e.g. `"org.eclipse.*"`), +or if no rule conditions match (fail-closed), Ditto injects an **empty namespaces set**, returning no results. +In this case, clients should provide explicit namespace values in the `namespaces` query parameter. + +#### WebSocket and SSE behavior + +Namespace access rules are evaluated once at **connection time** using the JWT present when the WebSocket or SSE +session is established. The validator is **not updated** when a JWT is refreshed mid-session; namespace access +continues to reflect the access granted at connect time. + +Namespace enforcement applies to incoming commands (things and policies) sent over WebSocket. Search commands +(`QueryThings`) are not blocked at the namespace level since they carry no entity ID; namespace filtering for +search is handled via the `namespaces` parameter instead. + +#### Configuration example + +```hocon +ditto.gateway.authentication { + namespace-access = [ + { + # Rule applies only when the JWT issuer matches + conditions = [ + "{%raw%}{{ jwt:iss | fn:filter('like','https://my-idp.example.com*') }}{%endraw%}" + ] + resource-types = ["thing", "policy"] + allowed-namespaces = [ + "org.example.*" + "concrete.namespace" + ] + blocked-namespaces = [ + "forbidden.namespace" + ] + } + ] +} +``` + +#### Using `fn:default` before `fn:filter` for optional headers + +When a condition references an HTTP header that may be absent, always add `fn:default` before `fn:filter`: + +``` +"{%raw%}{{ header:someheader | fn:default('safe') | fn:filter('ne','dangerous') }}{%endraw%}" +``` + +Without `fn:default`, an absent header produces an empty pipeline result which causes the entire condition to fail. +This would silently bypass the rule rather than enforcing it, which is a security footgun. + +#### Invalid namespace patterns + +If a namespace pattern (in `allowed-namespaces` or `blocked-namespaces`) is syntactically invalid, Ditto will fail +at startup with a `DittoConfigError`. This prevents operators from inadvertently deploying a configuration where +access control rules are silently skipped. + +### Namespace root policies + +Since Ditto *3.9.0*, operators can configure **namespace root policies** — pre-existing policies whose +`importable: implicit` entries are automatically merged into every policy in a matching namespace at enforcer-build +time. This enables cross-cutting access grants (e.g. a tenant-wide read subject) without modifying any stored +policy. + +For the end-user concept, see [basic-policy.html#namespace-root-policies](basic-policy.html#namespace-root-policies). + +#### Configuration + +The mapping is defined under `ditto.namespace-policies` in the `policies.conf`, `things.conf`, and `search.conf` +service configuration files (or overridden via a `-dev.conf` / extension config for a given deployment): + +```hocon +ditto.namespace-policies { + # Exact namespace match + "org.eclipse.ditto.devices" = ["org.eclipse.ditto.devices:devices-root"] + + # Prefix wildcard — applies to any sub-namespace of org.eclipse.ditto + # Does NOT match org.eclipse.ditto itself (requires at least one sub-segment) + "org.eclipse.ditto.*" = ["org.eclipse.ditto:tenant-root"] + + # Catch-all — applies to every namespace + # "*" = ["root:global-policy"] +} +``` + +Multiple patterns can match a single namespace. When they do, patterns are applied in deterministic precedence +order: exact match first, then prefix wildcards from most specific to least specific, then `"*"`. + +Multiple root policy IDs can be listed per pattern. They are applied left-to-right. + +#### Pattern syntax + +| Pattern | Matches | +|---|---| +| `"org.example.devices"` | Only the exact namespace `org.example.devices` | +| `"org.example.*"` | Any namespace starting with `org.example.` (requires at least one sub-segment) | +| `"*"` | Every namespace | + +Unsupported patterns (e.g. `"org.*.devices"` or `"foo*"`) are rejected at startup with a `DittoConfigError`. + +#### Behaviour details + +**Label conflict — local wins:** If a local policy already has an entry with the same label as a root policy +entry, the local entry is preserved unchanged. The root policy entry is silently skipped for that label. + +**Self-referential guard:** A root policy is never merged into itself. If `org.eclipse.ditto:tenant-root` is +configured for `"org.eclipse.ditto.*"`, it is not merged when building the enforcer for `tenant-root` itself. + +**Missing root policy:** If a configured root policy does not exist or cannot be loaded, its entries are skipped +and an ERROR is logged. The child policy's enforcer is still built successfully from its own entries. + +**Cache invalidation:** When a root policy is modified, Ditto automatically invalidates all cached enforcers for +policies in matching namespaces. This is an O(n) scan over the enforcer cache and happens transparently. +For very large deployments with frequently-changing root policies, consider keeping the root policies stable +and pushing changes via individual child policies instead. + +**Search consistency:** Root policy changes are reflected in the things-search index. The search service tracks +root policy revisions as part of the resolved-policy cache key, so a root policy update triggers re-indexing +of all affected things. + +#### Helm configuration + +For Helm deployments, configure namespace root policies in `values.yaml` via the shared `global` section: + +```yaml +global: + namespacePolicies: + "org.eclipse.ditto.*": + - "org.eclipse.ditto:tenant-root" +``` + +This shared mapping is rendered into the respective service extension config files under `ditto.namespace-policies`. + +## Further reading + +* [Operating - MongoDB](operating-mongodb.html) +* [Operating - Authentication](operating-authentication.html) +* [Operating - DevOps Commands](operating-devops.html) +* [Operating - Monitoring & Tracing](operating-monitoring.html) +* [Extending Ditto](installation-extending.html) diff --git a/documentation/src/main/resources/pages/ditto/operating-devops.md b/documentation/src/main/resources/pages/ditto/operating-devops.md new file mode 100644 index 00000000000..217eda477aa --- /dev/null +++ b/documentation/src/main/resources/pages/ditto/operating-devops.md @@ -0,0 +1,610 @@ +--- +title: Operating - DevOps Commands +tags: [installation] +keywords: operating, devops, piggyback, commands, logging, configuration, namespace, cleanup +permalink: operating-devops.html +--- + +You use DevOps commands to manage a running Ditto installation without restarts -- adjusting log levels, inspecting configuration, and sending piggyback commands to internal actors. + +{% include callout.html content="**TL;DR**: Access the `/devops` API with the DevOps user credentials to dynamically change log levels, retrieve runtime configuration, manage background cleanup, and execute piggyback commands against internal services." type="primary" %} + +## Overview + +The DevOps commands API lets you: + +* Dynamically retrieve and change log levels +* Retrieve service configuration at runtime +* Send piggyback commands to internal actors +* Manage background cleanup and search synchronization +* Erase data within a namespace + +## DevOps user + +[pubsubmediator]: https://pekko.apache.org/docs/pekko/current/distributed-pub-sub.html + +The DevOps user authenticates requests to these endpoints: + +```text +/devops +/api/2/connections +``` + +{% include note.html content="The default devops credentials are username: `devops`, password: `foobar`. The password can be changed by setting the environment variable `DEVOPS_PASSWORD` in the gateway service." %} + +## Dynamically adjust log levels + +Changing log levels at runtime is useful for debugging problems without restarting services. + +### Retrieve all log levels + +`GET /devops/logging` + +```json +{ + "gateway": { + "10.0.0.1": { + "type": "devops.responses:retrieveLoggerConfig", + "status": 200, + "serviceName": "gateway", + "instance": "10.0.0.1", + "loggerConfigs": [ + { "level": "info", "logger": "ROOT" }, + { "level": "info", "logger": "org.eclipse.ditto" }, + { "level": "warn", "logger": "org.mongodb.driver" } + ] + } + } +} +``` + +### Change a log level for all services + +`PUT /devops/logging` + +```json +{ + "logger": "org.eclipse.ditto", + "level": "debug" +} +``` + +### Retrieve log levels for one service + +`GET /devops/logging/gateway` + +Response example: + +```json +{ + "1": { + "type": "devops.responses:retrieveLoggerConfig", + "status": 200, + "serviceName": "gateway", + "instance": 1, + "loggerConfigs": [{ + "level": "info", + "logger": "ROOT" + }, { + "level": "info", + "logger": "org.eclipse.ditto" + }, { + "level": "warn", + "logger": "org.mongodb.driver" + }] + } +} +``` + +### Change a log level for one service + +`PUT /devops/logging/gateway` + +```json +{ + "logger": "org.eclipse.ditto", + "level": "debug" +} +``` + +## Dynamically retrieve configurations + +Access runtime configurations at `/devops/config/` with optional filters by service name, instance ID, and configuration path. + +### Retrieve configuration with a path filter + +`GET /devops/config?path=ditto.info` + +Always include the `path` parameter. Omitting it returns the full configuration of all services, which can be megabytes in size and may exceed the 250 kB cluster message limit. + +The path `ditto.info` returns service name, instance index, JVM arguments, and environment variables: + +```json +{ + "gateway": { + "10.0.0.1": { + "type": "common.responses:retrieveConfig", + "status": 200, + "config": { + "env": { "PATH": "/usr/games:/usr/local/games" }, + "service": { "instance-id": "10.0.0.1", "name": "gateway" }, + "vm-args": ["-Dfile.encoding=UTF-8"] + } + } + } +} +``` + +### Retrieve configuration for a specific instance + +`GET /devops/config/gateway/1?path=ditto` + +This is faster than retrieving from all instances because the response is not aggregated. + +Response example: + +```json +{ + "type": "common.responses:retrieveConfig", + "status": 200, + "config": { + "cluster": { + "number-of-shards": 20 + }, + "gateway": { + "authentication": { + "devops": { + "password": "foobar", + "secured": false + } + } + } + } +} +``` + +## Piggyback commands + +Piggyback commands let you send a command to any actor in the Ditto cluster. A piggyback command conforms to this schema: + +{% include docson.html schema="jsonschema/piggyback-command.json" %} + +### Headers + +| Header | Values | Default | Purpose | +|--------|--------|---------|---------| +| `is-group-topic` | `true` / `false` | `false` | Send to all actors in a group vs. one actor | +| `aggregate` | `true` / `false` | `true` | Aggregate responses from multiple actors | +| `ditto-sudo` | `true` / `false` | `false` | Bypass enforcement/authorization | + +### Managing policies + +Send any [PolicyCommand](https://github.com/eclipse-ditto/ditto/blob/master/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/PolicyCommand.java) as a piggyback. Use the **internal JSON representation** (from the `fromJson` methods), not the [Ditto Protocol](protocol-specification-policies.html) format. + +**Create a policy:** + +```json +{ + "targetActorSelection": "/system/sharding/policy", + "headers": { + "aggregate": false, + "is-group-topic": true, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "policies.commands:createPolicy", + "policy": { + "policyId": "", + "entries": {} + } + } +} +``` + +**Retrieve a policy:** + +```json +{ + "targetActorSelection": "/system/sharding/policy", + "headers": { + "aggregate": false, + "is-group-topic": true, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "policies.commands:retrievePolicy", + "policyId": "" + } +} +``` + +### Managing things + +Send any [ThingCommand](https://github.com/eclipse-ditto/ditto/blob/master/things/model/src/main/java/org/eclipse/ditto/things/model/signals/commands/ThingCommand.java) as a piggyback. Use the **internal JSON representation**. + +**Create a thing:** + +```json +{ + "targetActorSelection": "/system/sharding/thing", + "headers": { + "aggregate": false, + "is-group-topic": true, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "things.commands:createThing", + "thing": { + "thingId": "", + "policyId": "" + } + } +} +``` + +**Retrieve a thing:** + +```json +{ + "targetActorSelection": "/system/sharding/thing", + "headers": { + "aggregate": false, + "is-group-topic": true, + "ditto-sudo": true + }, + "piggybackCommand": { + "type": "things.commands:retrieveThing", + "thingId": "" + } +} +``` + +### Managing connections + +Use the [HTTP API](connectivity-manage-connections.html) for connection management. [Piggyback-based management](connectivity-manage-connections-piggyback.html) is also available. + +### Managing background cleanup + +Each Things, Policies, and Connectivity instance has a cleanup coordinator actor at `/user/Root/persistenceCleanup`. + +**Query cleanup state:** + +`POST /devops/piggyback/?timeout=10s` + +```json +{ + "targetActorSelection": "/user/Root/persistenceCleanup", + "headers": {}, + "piggybackCommand": { + "type": "status.commands:retrieveHealth" + } +} +``` + +Response example: + +```json +{ + "type": "status.responses:retrieveHealth", + "status": 200, + "statusInfo": { + "status": "UP", + "details": [ + { + "INFO": { + "state": "RUNNING", + "pid": "thing:org.eclipse.ditto:fancy-thing_53" + } + } + ] + } +} +``` + +**Query cleanup configuration:** + +```json +{ + "targetActorSelection": "/user/Root/persistenceCleanup", + "headers": {}, + "piggybackCommand": { + "type": "common.commands:retrieveConfig" + } +} +``` + +Response example: + +```json +{ + "type": "common.responses:retrieveConfig", + "status": 200, + "config": { + "enabled": true, + "interval": "3s", + "quiet-period": "5m", + "timer-threshold": "150ms", + "credits-per-batch": 3, + "reads-per-query": 100, + "writes-per-credit": 100, + "delete-final-deleted-snapshot": false + } +} +``` + +**Modify cleanup configuration:** + +```json +{ + "targetActorSelection": "/user/Root/persistenceCleanup", + "headers": { + "aggregate": false, + "is-group-topic": true + }, + "piggybackCommand": { + "type": "common.commands:modifyConfig", + "config": { + "quiet-period": "240d", + "last-pid": "thing:namespace:PID-lower-bound" + } + } +} +``` + +The response contains the effective configuration. If the configuration in the piggyback command contains any error, +an error is logged and the actor's configuration is unchanged. The field `last-pid` is not part of the configuration. + +```json +{ + "type": "common.responses:modifyConfig", + "status": 200, + "config": { + "enabled": true, + "interval": "3s", + "quiet-period": "240d", + "timer-threshold": "150ms", + "credits-per-batch": 3, + "reads-per-query": 100, + "writes-per-credit": 100, + "delete-final-deleted-snapshot": false + } +} +``` + +**Clean up a specific entity:** + +`POST /devops/piggyback/things/?timeout=10s` + +```json +{ + "targetActorSelection": "/system/sharding/thing", + "headers": { "aggregate": false }, + "piggybackCommand": { + "type": "cleanup.sudo.commands:cleanupPersistence", + "entityId": "ditto:thing1" + } +} +``` + +Response example: + +```json +{ + "type": "cleanup.sudo.responses:cleanupPersistence", + "status": 200, + "entityId": "thing:ditto:thing1" +} +``` + +### Managing background synchronization + +The background sync actor ensures eventual consistency of the search index. It responds to the same command types as the cleanup coordinator. + +`POST /devops/piggyback/search/?timeout=10s` + +```json +{ + "targetActorSelection": "/user/thingsWildcardSearchRoot/searchUpdaterRoot/backgroundSync/singleton", + "headers": { + "aggregate": false, + "is-group-topic": false + }, + "piggybackCommand": { + "type": "" + } +} +``` + +Supported `COMMAND-TYPE` values: `common.commands:shutdown`, `common.commands:retrieveConfig`, `common.commands:modifyConfig`, `status.commands:retrieveHealth`. + +{% include note.html content="Only a subset of the configuration has an effect when changed via `common.commands:modifyConfig` command: `enabled`, `quiet-period` and `keep.events`. Refer to [Ditto configuration](operating-configuration.html) for instructions how to modify the other configuration settings." %} + +### Force search index update + +**All things:** + +`POST /devops/piggyback/search?timeout=10s` + +```json +{ + "targetActorSelection": "/user/thingsWildcardSearchRoot/searchUpdaterRoot/backgroundSyncProxy", + "headers": { + "aggregate": false, + "is-group-topic": false, + "force-update": true + }, + "piggybackCommand": { + "type": "common.commands:shutdown" + } +} +``` + +**Things in specific namespaces:** + +`POST /devops/piggyback/search?timeout=10s` + +```json +{ + "targetActorSelection": "/user/thingsWildcardSearchRoot/searchUpdaterRoot/backgroundSyncProxy", + "headers": { + "aggregate": false, + "is-group-topic": false, + "force-update": true, + "namespaces": ["namespace1", "namespace2"] + }, + "piggybackCommand": { + "type": "common.commands:shutdown" + } +} +``` + +**A single thing:** + +`POST /devops/piggyback/search/?timeout=0` + +```json +{ + "targetActorSelection": "/user/thingsWildcardSearchRoot/searchUpdaterRoot/thingsUpdater", + "headers": { + "aggregate": false, + "is-group-topic": true + }, + "piggybackCommand": { + "type": "thing-search.sudo.commands:sudoUpdateThing", + "thingId": "" + } +} +``` + +## Erasing data within a namespace + +You can erase all data within a namespace during live operations by following these four steps in sequence. + +### Step 1: Block the namespace + +`PUT /devops/piggyback?timeout=10s` + +```json +{ + "targetActorSelection": "/system/distributedPubSubMediator", + "headers": { "aggregate": false }, + "piggybackCommand": { + "type": "namespaces.commands:blockNamespace", + "namespace": "namespaceToBlock" + } +} +``` + +The namespace stays blocked for the lifetime of the cluster or until you unblock it in step 4. + +Response: + +```json +{ + "type": "namespaces.responses:blockNamespace", + "status": 200, + "namespace": "namespaceToBlock", + "resourceType": "namespaces" +} +``` + +### Step 2: Shut down actors in the namespace + +`PUT /devops/piggyback?timeout=0` + +```json +{ + "targetActorSelection": "/system/distributedPubSubMediator", + "piggybackCommand": { + "type": "common.commands:shutdown", + "reason": { + "type": "purge-namespace", + "details": "namespaceToShutdown" + } + } +} +``` + +This command has no response (always returns `408` timeout). You can send it multiple times to ensure completion. + +### Step 3: Purge data from persistence + +`PUT /devops/piggyback?timeout=10s` + +```json +{ + "targetActorSelection": "/system/distributedPubSubMediator", + "headers": { + "aggregate": true, + "is-group-topic": true + }, + "piggybackCommand": { + "type": "namespaces.commands:purgeNamespace", + "namespace": "namespaceToPurge" + } +} +``` + +Set the timeout to a safe margin above the estimated erasure time. The response reports results per resource type. +Note that to see responses from multiple resource types, the header `aggregate` must not be `false`. + +```json +{ + "?": { + "?": { + "type": "namespaces.responses:purgeNamespace", + "status": 200, + "namespace": "namespaceToPurge", + "resourceType": "thing", + "successful": true + }, + "?1": { + "type": "namespaces.responses:purgeNamespace", + "status": 200, + "namespace": "namespaceToPurge", + "resourceType": "policy", + "successful": true + }, + "?2": { + "type": "namespaces.responses:purgeNamespace", + "status": 200, + "namespace": "namespaceToPurge", + "resourceType": "thing-search", + "successful": true + } + } +} +``` + +### Step 4: Unblock the namespace + +`PUT /devops/piggyback?timeout=10s` + +```json +{ + "targetActorSelection": "/system/distributedPubSubMediator", + "headers": { "aggregate": false }, + "piggybackCommand": { + "type": "namespaces.commands:unblockNamespace", + "namespace": "namespaceToUnblock" + } +} +``` + +Response: + +```json +{ + "type": "namespaces.responses:unblockNamespace", + "status": 200, + "namespace": "namespaceToUnblock", + "resourceType": "namespaces" +} +``` + +## Further reading + +* [Operating - Configuration](operating-configuration.html) +* [Operating - MongoDB](operating-mongodb.html) +* [Operating - Monitoring & Tracing](operating-monitoring.html) +* [Manage connections via HTTP](connectivity-manage-connections.html) diff --git a/documentation/src/main/resources/pages/ditto/operating-mongodb.md b/documentation/src/main/resources/pages/ditto/operating-mongodb.md new file mode 100644 index 00000000000..5c8cd2eb31f --- /dev/null +++ b/documentation/src/main/resources/pages/ditto/operating-mongodb.md @@ -0,0 +1,140 @@ +--- +title: Operating - MongoDB +tags: [installation] +keywords: operating, mongodb, database, configuration, tuning, indexes +permalink: operating-mongodb.html +--- + +You configure Ditto's MongoDB connection and tune database performance through environment variables and config files. + +{% include callout.html content="**TL;DR**: Set `MONGO_DB_URI` and related environment variables to connect to your MongoDB instance. For MongoDB 6+, enable additional snapshot aggregation indexes to maintain query performance." type="primary" %} + +## Overview + +Each Ditto microservice that persists data connects to MongoDB. If you use a dedicated MongoDB instance instead of the bundled container, you control the connection through environment variables. + +## Configuration + +### Connection settings + +Set these environment variables to configure the MongoDB connection: + +| Variable | Purpose | +|----------|---------| +| `MONGO_DB_URI` | Connection string to MongoDB | +| `MONGO_DB_SSL_ENABLED` | Enable SSL connection | +| `MONGO_DB_CONNECTION_MIN_POOL_SIZE` | Minimum connection pool size | +| `MONGO_DB_CONNECTION_POOL_SIZE` | Connection pool size | +| `MONGO_DB_READ_PREFERENCE` | Read preference setting | +| `MONGO_DB_WRITE_CONCERN` | Write concern setting | +| `PEKKO_PERSISTENCE_MONGO_JOURNAL_WRITE_CONCERN` | Pekko Persistence journal write concern | +| `PEKKO_PERSISTENCE_MONGO_SNAPS_WRITE_CONCERN` | Pekko Persistence snapshot write concern | + +### Passwordless authentication via AWS IAM + +Since Ditto 3.6.0, you can [authenticate with AWS IAM](https://www.mongodb.com/docs/atlas/security/aws-iam-authentication/) instead of using username/password credentials. + +To enable this: + +1. Configure your Kubernetes service account with the role ARN via the annotation `eks.amazonaws.com/role-arn`. +2. Configure Ditto's services to assume that role during MongoDB authentication. + +**Environment variables:** + +| Variable | Purpose | +|----------|---------| +| `MONGO_DB_USE_AWS_IAM_ROLE` | Enable AWS IAM role authentication (boolean) | +| `MONGO_DB_AWS_REGION` | AWS region | +| `MONGO_DB_AWS_ROLE_ARN` | ARN of the IAM role to assume | +| `MONGO_DB_AWS_SESSION_NAME` | AWS session name for the assumed role | + +**Helm chart:** Configure the `serviceAccount` key to annotate the Kubernetes service account, and the `dbconfig` key for each Ditto service in your `values.yaml`. + +## Tuning + +### Background aggregation queries + +Ditto runs background `aggregate` queries against MongoDB to clean up data based on current database load. These queries primarily target snapshot collections (`things_snaps`, `policies_snaps`, `connections_snaps`). + +#### MongoDB 5 + +The default settings work well. Aggregation queries run quickly without excessive disk read operations. + +#### MongoDB 6+ + +In MongoDB 6, the same aggregation queries can slow down significantly with large snapshot stores, causing increased disk read IOPS. Enable additional indexes to restore performance: + +**Environment variables:** + +```bash +MONGODB_READ_JOURNAL_SHOULD_CREATE_ADDITIONAL_SNAPSHOT_AGGREGATION_INDEX_PID_ID=true +MONGODB_READ_JOURNAL_SHOULD_CREATE_ADDITIONAL_SNAPSHOT_AGGREGATION_INDEX_PID_SN=true +MONGODB_READ_JOURNAL_SHOULD_CREATE_ADDITIONAL_SNAPSHOT_AGGREGATION_INDEX_PID_SN_ID=true +``` + +**Helm values (example for the `things` service):** + +```yaml +things: + config: + readJournal: + indexes: + createSnapshotAggregationIndexPidId: true + createSnapshotAggregationIndexPidSn: true + createSnapshotAggregationIndexPidSnId: true +``` + +### Background cleanup + +Ditto deletes unnecessary events and snapshots in the background according to database load. This cleanup is available for Policies, Things, and Connectivity services. + +Key configuration parameters: + +```hocon +cleanup { + enabled = true + enabled = ${?CLEANUP_ENABLED} + + # How long to keep events/snapshots before cleanup + history-retention-duration = 3d + history-retention-duration = ${?CLEANUP_HISTORY_RETENTION_DURATION} + + # Pause between cleanup runs + quiet-period = 5m + quiet-period = ${?CLEANUP_QUIET_PERIOD} + + # How often to issue cleanup credits + interval = 3s + interval = ${?CLEANUP_INTERVAL} + + # Max DB latency before pausing cleanup + timer-threshold = 150ms + timer-threshold = ${?CLEANUP_TIMER_THRESHOLD} + + # Credits issued per interval + credits-per-batch = 3 + credits-per-batch = ${?CLEANUP_CREDITS_PER_BATCH} + + # Snapshots scanned per query + reads-per-query = 100 + reads-per-query = ${?CLEANUP_READS_PER_QUERY} + + # Documents deleted per credit + writes-per-credit = 100 + writes-per-credit = ${?CLEANUP_WRITES_PER_CREDIT} + + # Whether to delete the final "deleted" snapshot + delete-final-deleted-snapshot = false + delete-final-deleted-snapshot = ${?CLEANUP_DELETE_FINAL_DELETED_SNAPSHOT} +} +``` + +By default, the retention duration is `0d`, meaning no history is kept. To use Ditto's [history capabilities](basic-history.html), adjust `history-retention-duration` accordingly. + +You can also manage background cleanup at runtime through [DevOps piggyback commands](operating-devops.html). + +## Further reading + +* [Operating - Configuration](operating-configuration.html) +* [Operating - DevOps Commands](operating-devops.html) +* [Operating - Monitoring & Tracing](operating-monitoring.html) diff --git a/documentation/src/main/resources/pages/ditto/operating-monitoring.md b/documentation/src/main/resources/pages/ditto/operating-monitoring.md new file mode 100644 index 00000000000..3cb84a62f99 --- /dev/null +++ b/documentation/src/main/resources/pages/ditto/operating-monitoring.md @@ -0,0 +1,232 @@ +--- +title: Operating - Monitoring & Tracing +tags: [installation] +keywords: operating, monitoring, logging, tracing, prometheus, grafana, metrics, opentelemetry, elk, logstash +permalink: operating-monitoring.html +--- + +You monitor Ditto through structured logging, Prometheus metrics, and OpenTelemetry-based distributed tracing. + +{% include callout.html content="**TL;DR**: Ditto exposes Prometheus metrics on port 9095 by default. Enable tracing with `DITTO_TRACING_ENABLED=true` and point the OTLP exporter to your collector. Configure logging output via environment variables for STDOUT, ELK, or file-based appenders." type="primary" %} + +## Overview + +Ditto provides three observability pillars: + +* **Logging**: STDOUT, ELK (Logstash), or file-based log output +* **Monitoring**: Prometheus-compatible metrics endpoint +* **Tracing**: W3C Trace Context propagation with OpenTelemetry export + +## Logging + +### Log output options + +You can gather logs from a running Ditto installation in three ways: + +**STDOUT/STDERR (default):** +* Works with all Docker logging drivers (awslogs, splunk, etc.) +* Disable with `DITTO_LOGGING_DISABLE_SYSOUT_LOG=true` + +**ELK stack (Logstash):** +* Set `DITTO_LOGGING_LOGSTASH_SERVER` to your Logstash endpoint + +**Log files:** +* Enable with `DITTO_LOGGING_FILE_APPENDER=true` +* Log files use LogstashEncoder format for easy ELK import + +### File appender configuration + +When you enable file-based logging, configure these environment variables: + +| Variable | Default | Purpose | +|----------|---------|---------| +| `DITTO_LOGGING_FILE_APPENDER_THRESHOLD` | `info` | Minimum log level | +| `DITTO_LOGGING_FILE_NAME_PATTERN` | `/var/log/ditto/.log.%d{yyyy-MM-dd}.gz` | File name pattern (rollover period inferred from pattern) | +| `DITTO_LOGGING_MAX_LOG_FILE_HISTORY` | `10` | Maximum number of archived log files | +| `DITTO_LOGGING_TOTAL_LOG_FILE_SIZE` | `1GB` | Total disk space for log files (requires `MAX_LOG_FILE_HISTORY` to be set) | +| `DITTO_LOGGING_CLEAN_HISTORY_ON_START` | `false` | Delete old logs on service start | + +See the [logback documentation](https://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy) for details on these settings. + +When running Ditto in Kubernetes, apply the `ditto-log-files.yaml` manifest to mount log files to the host system. + +### Dynamic log level changes + +You can change log levels at runtime without restarting services through the [DevOps commands API](operating-devops.html). + +## Monitoring + +### How it works + +Each Ditto service exposes a Prometheus-compatible HTTP endpoint where metrics are published automatically. Prometheus scrapes these endpoints, and you visualize the data with tools like [Grafana](https://grafana.com). + +### Configuration + +Each service publishes metrics on port `9095` by default. Change this with the `PROMETHEUS_PORT` environment variable. + +The metrics endpoint is available at: +```text +http://:9095/ +``` + +### Gathered metrics + +Visit the Prometheus endpoint of any service to see the full list of exported metrics. The following +excerpt shows metrics gathered for the [gateway service](architecture-services-gateway.html): + +``` +#Kamon Metrics +# TYPE jvm_threads gauge +jvm_threads{component="system-metrics",measure="total"} 72.0 +# TYPE jvm_memory_buffer_pool_count gauge +jvm_memory_buffer_pool_count{component="system-metrics",pool="direct"} 14.0 +# TYPE jvm_class_loading gauge +jvm_class_loading{component="system-metrics",mode="loaded"} 10491.0 +# TYPE jvm_memory_buffer_pool_usage gauge +jvm_memory_buffer_pool_usage{component="system-metrics",pool="direct",measure="used"} 396336.0 +# TYPE roundtrip_http_seconds histogram +roundtrip_http_seconds_bucket{le="0.05",ditto_request_path="/api/2/things/x",ditto_request_method="PUT",ditto_statusCode="201",segment="overall"} 1.0 +roundtrip_http_seconds_sum{ditto_request_path="/api/2/things/x",ditto_statusCode="201",ditto_request_method="PUT",segment="overall"} 0.038273024 +roundtrip_http_seconds_bucket{le="0.001",ditto_request_path="/api/2/things/x",ditto_request_method="PUT",ditto_statusCode="204",segment="overall"} 0.0 +roundtrip_http_seconds_bucket{le="0.1",ditto_request_path="/api/2/things/x",ditto_request_method="PUT",ditto_statusCode="204",segment="overall"} 7.0 +roundtrip_http_seconds_sum{ditto_request_path="/api/2/things/x",ditto_statusCode="204",ditto_request_method="PUT",segment="overall"} 0.828899328 +# TYPE jvm_gc_promotion histogram +jvm_gc_promotion_sum{space="old"} 7315456.0 +# TYPE jvm_gc_seconds histogram +jvm_gc_seconds_count{component="system-metrics",collector="scavenge"} 9.0 +jvm_gc_seconds_sum{component="system-metrics",collector="scavenge"} 0.063 +# TYPE jvm_memory_bytes histogram +jvm_memory_bytes_count{component="system-metrics",measure="used",segment="miscellaneous-non-heap-storage"} 54.0 +jvm_memory_bytes_sum{component="system-metrics",measure="used",segment="miscellaneous-non-heap-storage"} 786350080.0 +``` + +Ditto reports: + +* **JVM metrics** (all services): garbage collection counts and times, memory consumption (heap and non-heap), thread counts, loaded classes +* **HTTP metrics** ([gateway service](architecture-services-gateway.html)): roundtrip times, request counts, response status codes +* **MongoDB metrics** ([things](architecture-services-things.html), [policies](architecture-services-policies.html), [things-search](architecture-services-things-search.html)): inserts, updates, reads per second, roundtrip times +* **Connection metrics** ([connectivity service](architecture-services-connectivity.html)): processed messages, mapping times + +Explore the [example Grafana dashboards](https://github.com/eclipse-ditto/ditto/tree/master/deployment/operations/grafana-dashboards) and contribute new ones back to the community. + +### Custom metrics + +Since Ditto 3.5.0, you can define custom metrics that count things matching a namespace/filter combination. Configure these in the [search service](architecture-services-things-search.html). + +```hocon +ditto { + search { + operator-metrics { + enabled = true + scrape-interval = 15m + custom-metrics { + all_produced_and_not_installed_devices { + scrape-interval = 5m + namespaces = [ + "org.eclipse.ditto.smokedetectors" + ] + filter = "and(exists(attributes/production-date),not(exists(attributes/installation-date)))" + tags { + company = "acme-corp" + } + } + } + } + } +} +``` + +Ditto performs a [count things operation](basic-search.html#search-count-queries) at the configured interval and exposes the result as a Prometheus gauge: + +```text +all_produced_and_not_installed_devices{company="acme-corp"} 42.0 +``` + +To add custom metrics via system properties: +``` +-Dditto.search.operator-metrics.custom-metrics.all_produced_and_not_installed_devices.enabled=true +-Dditto.search.operator-metrics.custom-metrics.all_produced_and_not_installed_devices.scrape-interval=5m +-Dditto.search.operator-metrics.custom-metrics.all_produced_and_not_installed_devices.namespaces.0=org.eclipse.ditto.smokedetectors +-Dditto.search.operator-metrics.custom-metrics.all_produced_and_not_installed_devices.namespaces.1=org.eclipse.ditto.cameras +-Dditto.search.operator-metrics.custom-metrics.all_produced_and_not_installed_devices.filter=and(exists(attributes/production-date),not(exists(attributes/installation-date))) +-Dditto.search.operator-metrics.custom-metrics.all_produced_and_not_installed_devices.tags.company=acme-corp +``` + +### Custom aggregation metrics + +Since Ditto 3.6.0, you can define aggregation-based metrics with dynamic tags populated from thing data using the `group-by` placeholder. + +{% include note.html content="**Since Ditto 3.7.3** there is a change in how the custom aggregation metrics are configured. See the [Release notes of 3.7.3](release_notes_373.html) for details on migration steps." %} + +{% include warning.html content="Avoid grouping by fields with high cardinality, as this creates many metric series and may overload your Prometheus server." %} + +```hocon +ditto { + search { + operator-metrics { + custom-aggregation-metrics { + online_things { + enabled = true + scrape-interval = 20m + namespaces = ["org.eclipse.ditto"] + group-by { + "location" = "attributes/Info/location" + "isGateway" = "attributes/Info/gateway" + } + tags { + "hardcoded-tag" = "hardcoded_value" + "location" = "{%raw%}{{ group-by:location | fn:default('missing location') }}{%endraw%}" + "isGateway" = "{%raw%}{{ group-by:isGateway }}{%endraw%}" + } + filter = "gt(features/ConnectionStatus/properties/status/readyUntil,time:now)" + } + } + } + } +} +``` + +This produces metrics like: + +```text +online_things{location="Berlin",isGateway="false",hardcoded-tag="hardcoded_value"} 6.0 +online_things{location="Immenstaad",isGateway="true",hardcoded-tag="hardcoded_value"} 8.0 +``` + +To add custom aggregation metrics via system properties: +``` +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.enabled=true +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.scrape-interval=20m +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.namespaces.0=org.eclipse.ditto +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.group-by.location="attributes/Info/location" +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.group-by.isGateway="attributes/Info/gateway" +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.tags.hardcoded-tag="hardcoded_value" +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.tags.location="{%raw%}{{ group-by:location | fn:default('missing location') }}{%endraw%}" +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.tags.isGateway="{%raw%}{{ group-by:isGateway }}{%endraw%}" +-Dditto.search.operator-metrics.custom-aggregation-metrics.online_status.filter=gt(features/ConnectionStatus/properties/status/readyUntil/,time:now) +``` + +[Function expressions](basic-placeholders.html#function-expressions) are supported for transforming placeholder values. + +## Tracing + +### How it works + +Ditto supports [W3C Trace Context](https://www.w3.org/TR/trace-context/) headers at the edges of the system (Gateway and Connectivity services). Spans are generated during request processing and exported in [OpenTelemetry](https://opentelemetry.io/) format. + +### Configuration + +| Variable | Default | Purpose | +|----------|---------|---------| +| `DITTO_TRACING_ENABLED` | `false` | Enable tracing | +| `DITTO_TRACING_SAMPLER` | `never` | Sampler type: `always`, `never`, `random`, `adaptive` | +| `DITTO_TRACING_RANDOM_SAMPLER_PROBABILITY` | -- | Probability for `random` sampler | +| `DITTO_TRACING_ADAPTIVE_SAMPLER_THROUGHPUT` | -- | Target throughput for `adaptive` sampler | +| `DITTO_TRACING_OTEL_TRACE_REPORTER_ENABLED` | `false` | Enable OTLP trace reporting | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | OTLP collector endpoint | + +## Further reading + +* [Operating - Configuration](operating-configuration.html) +* [Operating - DevOps Commands](operating-devops.html) +* [Example Grafana dashboards](https://github.com/eclipse-ditto/ditto/tree/master/deployment/operations/grafana-dashboards) diff --git a/documentation/src/main/resources/pages/ditto/presentations.md b/documentation/src/main/resources/pages/ditto/presentations.md index 0eb4db8e8a9..592fdceec7e 100644 --- a/documentation/src/main/resources/pages/ditto/presentations.md +++ b/documentation/src/main/resources/pages/ditto/presentations.md @@ -5,103 +5,106 @@ permalink: presentations.html topnav: topnav --- -This page contains a collection of presentations, videos and workshops about Eclipse Ditto, sorted from most recent ones downwards. +Browse presentations, videos, and workshops about Eclipse Ditto, sorted from most recent to oldest. -## 27.02.2024 Eclipse Ditto project update in Eclipse IoT monthly WG meeting +{% include callout.html content="**TL;DR**: Browse slide decks and recorded talks covering Eclipse Ditto -- from project status updates at EclipseCon to deep-dive introductions and hands-on workshops." type="primary" %} -Topic: Status of the project in 2024 +## 2024 -The slides can be found here: [2024_02_27_eclipse-iot-wg-update](slides/2024_02_27_eclipse-iot-wg-update/index.html). +### Eclipse IoT WG Meeting -- Project Update (February 27, 2024) -## 26.01.2023 Eclipse Ditto in 30 minutes +Status of the project in 2024. -Topic: Short 30 minutes overview of Eclipse Ditto +Slides: [2024_02_27_eclipse-iot-wg-update](slides/2024_02_27_eclipse-iot-wg-update/index.html) -The slides can be found here: [2023_01_ditto-in-30-min](slides/2023_01_ditto-in-30-min/index.html). +## 2023 +### Eclipse Ditto in 30 Minutes (January 26, 2023) -## 24.10.2022 EclipseCon Europe 2022 Community Day +A short 30-minute overview of Eclipse Ditto. -Topic: Project status update of Eclipse Ditto +Slides: [2023_01_ditto-in-30-min](slides/2023_01_ditto-in-30-min/index.html) -The slides can be found here: [2022_10_24_eclipse-iot-wg-community-day](slides/2022_10_24_eclipse-iot-wg-community-day/index.html). +## 2022 +### EclipseCon Europe 2022 Community Day (October 24, 2022) -## 13.10.2022 Web of Things Community Meetup 1 - Digital Twins +Project status update of Eclipse Ditto. -Topic: Eclipse Ditto and Web of Things +Slides: [2022_10_24_eclipse-iot-wg-community-day](slides/2022_10_24_eclipse-iot-wg-community-day/index.html) -The slides can be found here: [2022_10_ditto-and-wot](slides/2022_10_ditto-and-wot/index.html). +### Web of Things Community Meetup 1 -- Digital Twins (October 13, 2022) +Eclipse Ditto and Web of Things. -## 25.10.2021 EclipseCon Europe 2021 Community Day +Slides: [2022_10_ditto-and-wot](slides/2022_10_ditto-and-wot/index.html) -Topic: Project status update of Eclipse Ditto +## 2021 -The slides can be found here: [2021_10_25-eclipse-iot-wg-community-day](slides/2021_10_25-eclipse-iot-wg-community-day/index.html). +### EclipseCon Europe 2021 Community Day (October 25, 2021) +Project status update of Eclipse Ditto. -## 22.06.2021 Eclipse IoT WorkingGroup status update +Slides: [2021_10_25-eclipse-iot-wg-community-day](slides/2021_10_25-eclipse-iot-wg-community-day/index.html) -Topic: Eclipse Ditto™ 2.0: Changes and roadmap +### Eclipse IoT WG Status Update (June 22, 2021) -The slides can be found here: [2021_06_ditto-20-overview](slides/2021_06_ditto-20-overview/index.html). +Eclipse Ditto 2.0: Changes and roadmap. +Slides: [2021_06_ditto-20-overview](slides/2021_06_ditto-20-overview/index.html) -## 19.10.2020 EclipseCon Europe 2020 Community Day +## 2020 -Topic: Project status update of Eclipse Ditto +### EclipseCon Europe 2020 Community Day (October 19, 2020) -The slides can be found here: [2020_10_19-eclipse-iot-wg-community-day](slides/2020_10_19-eclipse-iot-wg-community-day/index.html). +Project status update of Eclipse Ditto. +Slides: [2020_10_19-eclipse-iot-wg-community-day](slides/2020_10_19-eclipse-iot-wg-community-day/index.html) -## 28.07.2020 Eclipse IoT WorkingGroup status update +### Eclipse IoT WG Status Update (July 28, 2020) -Topic: Eclipse Ditto: Eclipse IoT WG status update +Eclipse Ditto: Eclipse IoT WG status update. -The slides can be found here: [2020_07_28-iot-wg-status-update](slides/2020_07_28-iot-wg-status-update/index.html). +Slides: [2020_07_28-iot-wg-status-update](slides/2020_07_28-iot-wg-status-update/index.html) +### The Things Network Virtual Conference -- #StayHome (April 16, 2020) -## 16.04.2020 The Things Network Virtual Conference 2020 #StayHome - -Topic: Eclipse Ditto: Digital Twins as part of an open IoT platform +Eclipse Ditto: Digital Twins as part of an open IoT platform. {% include youtube.html youtube-id="D33JrN2RWiI" width="560" height="315" %} -The slides can be found here: [2020_04_16-ttn-virtual-conference](slides/2020_04_16-ttn-virtual-conference/index.html). +Slides: [2020_04_16-ttn-virtual-conference](slides/2020_04_16-ttn-virtual-conference/index.html) -The blogpost for the hands-on part, connecting Ditto to The Things Network via MQTT can be found -[here](2020-04-16-connecting-to-ttn-via-mqtt.html). +The hands-on blog post about connecting Ditto to The Things Network via MQTT is available [here](2020-04-16-connecting-to-ttn-via-mqtt.html). -## 21.10.2019 EclipseCon Europe 2019 in Ludwigsburg +## 2019 -Topic: Project status update of Eclipse Ditto +### EclipseCon Europe 2019 in Ludwigsburg (October 21, 2019) -The slides can be found here: [2019_10_21-eclipse-iot-wg-f2f-ludwigsburg](slides/2019_10_21-eclipse-iot-wg-f2f-ludwigsburg/index.html). +Project status update of Eclipse Ditto. +Slides: [2019_10_21-eclipse-iot-wg-f2f-ludwigsburg](slides/2019_10_21-eclipse-iot-wg-f2f-ludwigsburg/index.html) -## 22.10.2018 EclipseCon Europe 2018 in Ludwigsburg +## 2018 -Topic: Project status update of Eclipse Ditto +### EclipseCon Europe 2018 in Ludwigsburg (October 22, 2018) -The slides can be found here: [2018_10_22-eclipse-iot-wg-f2f-ludwigsburg](slides/2018_10_22-eclipse-iot-wg-f2f-ludwigsburg/index.html). +Project status update of Eclipse Ditto. -## 23.05.2018 IoT Hessen Meetup during webweek rhein-main /18 +Slides: [2018_10_22-eclipse-iot-wg-f2f-ludwigsburg](slides/2018_10_22-eclipse-iot-wg-f2f-ludwigsburg/index.html) -Topic: "Digital twins with Eclipse Ditto" +### IoT Hessen Meetup -- webweek rhein-main (May 23, 2018) -Organized via [Meetup](https://www.meetup.com/IoT-Hessen/events/248886802/) by Diana Kupfer from codecentric. +"Digital twins with Eclipse Ditto" -- organized via [Meetup](https://www.meetup.com/IoT-Hessen/events/248886802/) by Diana Kupfer from codecentric. -The slides can be found here: [2018_05_23-meetup-iot-hessen](slides/2018_05_23-meetup-iot-hessen/index.html). +Slides: [2018_05_23-meetup-iot-hessen](slides/2018_05_23-meetup-iot-hessen/index.html) -The code of the showed live demo can be found in our [ditto-examples](https://github.com/eclipse-ditto/ditto-examples/tree/master/octopus-via-hono). +The live demo code is available in [ditto-examples](https://github.com/eclipse-ditto/ditto-examples/tree/master/octopus-via-hono). -## 07.02.2018 Virtual IoT Meetup +### Virtual IoT Meetup (February 7, 2018) -Topic: "Digital twins go open source: Eclipse Ditto introduction" -u -On 07.02.2018 we gave an introduction into Eclipse Ditto via [Meetup](https://www.meetup.com/Virtual-IoT/events/247048104/). +"Digital twins go open source: Eclipse Ditto introduction" -- presented via [Meetup](https://www.meetup.com/Virtual-IoT/events/247048104/). {% include youtube.html youtube-id="NpC4ROGqwKc" width="560" height="315" %} -The slides can be found here: [2018_02_07-virtualiot-meetup](slides/2018_02_07-virtualiot-meetup/index.html). +Slides: [2018_02_07-virtualiot-meetup](slides/2018_02_07-virtualiot-meetup/index.html) diff --git a/documentation/src/main/resources/pages/ditto/protocol-bindings.md b/documentation/src/main/resources/pages/ditto/protocol-bindings.md index e31faa41904..2bd1bfc1bd2 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-bindings.md +++ b/documentation/src/main/resources/pages/ditto/protocol-bindings.md @@ -5,31 +5,40 @@ tags: [protocol] permalink: protocol-bindings.html --- -A protocol binding defines how the Ditto protocol messages are transported using a specific network protocol e.g. -"Ditto Protocol over WebSocket". -The binding defines a set of rules how Ditto protocol messages are mapped to network protocol messages and back. +A protocol binding defines how you transport Ditto Protocol messages over a specific network protocol (e.g., "Ditto Protocol over WebSocket"). -Currently the following protocol bindings are supported: +{% include callout.html content="**TL;DR**: Ditto supports seven protocol bindings -- WebSocket, AMQP 1.0, AMQP 0.9.1, MQTT 3.1.1, MQTT 5, HTTP 1.1, and Kafka 2.x. Each binding maps Ditto Protocol JSON messages to its transport format." type="primary" %} -* [WebSocket](httpapi-protocol-bindings-websocket.html) -* [AMQP 1.0](connectivity-protocol-bindings-amqp10.html) -* [AMQP 0.9.1](connectivity-protocol-bindings-amqp091.html) -* [MQTT 3.1.1](connectivity-protocol-bindings-mqtt.html) -* [MQTT 5](connectivity-protocol-bindings-mqtt5.html) -* [HTTP 1.1](connectivity-protocol-bindings-http.html) -* [Kafka 2.x](connectivity-protocol-bindings-kafka2.html) +## Overview +Each binding specifies rules for mapping Ditto Protocol messages to transport-specific messages and back. The Ditto Protocol message format stays the same; only the transport wrapper changes. -## Content Type +## Supported bindings -When sending messages towards Ditto, the following content type has to be specified in a protocol-specific way given -that the messages are already in [Ditto Protocol](protocol-overview.html) format. +| Binding | Documentation | +|---|---| +| WebSocket | [WebSocket binding](httpapi-protocol-bindings-websocket.html) | +| AMQP 1.0 | [AMQP 1.0 binding](connectivity-protocol-bindings-amqp10.html) | +| AMQP 0.9.1 | [AMQP 0.9.1 binding](connectivity-protocol-bindings-amqp091.html) | +| MQTT 3.1.1 | [MQTT 3.1.1 binding](connectivity-protocol-bindings-mqtt.html) | +| MQTT 5 | [MQTT 5 binding](connectivity-protocol-bindings-mqtt5.html) | +| HTTP 1.1 | [HTTP 1.1 binding](connectivity-protocol-bindings-http.html) | +| Kafka 2.x | [Kafka 2.x binding](connectivity-protocol-bindings-kafka2.html) | -All [change notifications](basic-changenotifications.html) emitted by Ditto will also contain the `content-type`: +## Content type -``` +When you send messages that are already in [Ditto Protocol](protocol-overview.html) format, specify this content type in the transport-specific way: + +```text application/vnd.eclipse.ditto+json ``` -For messages which are not yet in that format, the [payload mapping](connectivity-mapping.html) of Ditto's -[connectivity](connectivity-overview.html) may be used to bring the messages in that format. +All [change notifications](basic-changenotifications.html) emitted by Ditto also carry this content type. + +For messages that are not yet in Ditto Protocol format, use [payload mapping](connectivity-mapping.html) in Ditto's [connectivity](connectivity-overview.html) service to transform them. + +## Further reading + +- [Protocol overview](protocol-overview.html) -- introduction to the Ditto Protocol +- [Protocol specification](protocol-specification.html) -- the full message format reference +- [Payload mapping](connectivity-mapping.html) -- transforming non-Ditto messages diff --git a/documentation/src/main/resources/pages/ditto/protocol-examples.md b/documentation/src/main/resources/pages/ditto/protocol-examples.md index 71b9dcd19ac..a2fdf712541 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-examples.md +++ b/documentation/src/main/resources/pages/ditto/protocol-examples.md @@ -5,30 +5,32 @@ tags: [protocol] permalink: protocol-examples.html --- -The structure of the examples in this section is as follows: +This page explains the structure of Ditto Protocol examples and provides reference samples for commands, responses, events, and acknowledgements. + +{% include callout.html content="**TL;DR**: Every protocol interaction follows the pattern: send a **command**, receive a **response** (success or error), and optionally observe an **event**. Use `correlation-id` to link related messages." type="primary" %} + +## Overview + +Each example in this section follows the same structure: a command that initiates an operation, a response indicating the outcome, and (for modifying commands) an event that subscribers receive. ## Command -Each example always starts with a command message that initiates an operation at Ditto -(e.g. create a thing, retrieve a thing). +Every interaction starts with a command message that tells Ditto what to do (e.g., create a Thing, modify an attribute). Commands include a `correlation-id` header to link the command with its response. ```json { "topic": "org.eclipse.ditto/fancy-thing/things/twin/commands/modify", "headers": { "correlation-id": "a780b7b5-fdd2-4864-91fc-80df6bb0a636", - "requested-acks": [ "twin-persisted","custom-ack" ] + "requested-acks": [ "twin-persisted", "custom-ack" ] }, "path": "/" - ... } ``` ## Response -A command always has a response which either reports the success or the failure. -The example contains the success response. -See Thing Error responses for examples of messages that will be returned in case of an error. +Every command produces a response indicating success or failure. The response carries the same `correlation-id` as the command. See [Things error responses](protocol-examples-errorresponses.html) for error examples. ```json { @@ -36,17 +38,14 @@ See Thing Error responses for examples of messages that will be returned in case "correlation-id": "a780b7b5-fdd2-4864-91fc-80df6bb0a636" }, "path": "/", - "value": { - ... - }, + "value": {}, "status": 204 } ``` ## Event -If Ditto triggers an event (e.g. Thing created, Attribute modified) as a result of the executed command, -an example of such an event is also demonstrated. +When a command modifies state (e.g., Thing created, attribute modified), Ditto emits an event. Subscribers who registered for change notifications receive these events. ```json { @@ -56,19 +55,16 @@ an example of such an event is also demonstrated. "requested-acks": [ "custom-ack" ] }, "path": "/", - "value": { - ... - }, + "value": {}, "revision": 1 } ``` ## Acknowledgements (ACKs) -A command issuer can require a response and specify the acknowledgements (ACKs) which have to be successfully fulfilled -to regard the command as successfully executed. +When you specify `requested-acks` in your command headers, Ditto collects acknowledgements from the processing chain. Subscribers that handle requested labels send acknowledgements back with matching `correlation-id` headers. -Below an example is given for a successfully fulfilled ACK (status 202): +**Successful ACK** (status `202`): ```json { @@ -81,7 +77,7 @@ Below an example is given for a successfully fulfilled ACK (status 202): } ``` -And here is an example for a failed ACK (aka NACK, status 400): +**Failed ACK / NACK** (status `400`): ```json { @@ -95,7 +91,7 @@ And here is an example for a failed ACK (aka NACK, status 400): } ``` -An ACK representing a timeout would look like this: +**Timeout ACK** (status `408`): ```json { @@ -113,3 +109,10 @@ An ACK representing a timeout would look like this: "status": 408 } ``` + +## Further reading + +- [Things specification](protocol-specification-things.html) -- all Thing commands with example links +- [Policies specification](protocol-specification-policies.html) -- all Policy commands with example links +- [Acknowledgements](basic-acknowledgements.html) -- ack concepts and configuration +- [Protocol specification](protocol-specification.html) -- the full message format reference diff --git a/documentation/src/main/resources/pages/ditto/protocol-overview.md b/documentation/src/main/resources/pages/ditto/protocol-overview.md index 7412cf2d031..203be22770e 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-overview.md +++ b/documentation/src/main/resources/pages/ditto/protocol-overview.md @@ -5,19 +5,44 @@ tags: [protocol] permalink: protocol-overview.html --- -The Ditto Protocol defines a JSON based text protocol for communicating with **digital twins** and the actual physical -devices they mirror. +The Ditto Protocol is a JSON-based text protocol for communicating with digital twins and the physical devices they represent. -It defines several **commands** both the actual device and the **digital twin** are able to understand. +{% include callout.html content="**TL;DR**: The Ditto Protocol defines a structured JSON message format for sending commands, receiving responses, and subscribing to events on digital twins and live devices." type="primary" %} -The communication pattern is defined by the Ditto protocol and shown in the next section. +## Overview +Every interaction with Eclipse Ditto -- whether you create a thing, update a sensor value, or query device state -- uses the Ditto Protocol. It provides a uniform message format regardless of the transport layer (WebSocket, AMQP, MQTT, HTTP, or Kafka). -## Communication pattern +The protocol defines several **command** types that both the digital twin and the actual physical device can understand. -The typical communication pattern when interacting with a **digital twin** or the actual device using the Ditto Protocol -is composed of multiple correlated Protocol messages. -Therefore, each Protocol message contains a `correlation-id` which can be used to associate related Protocol messages. +## How it works -The [Signals](basic-signals.html#communication-pattern) chapter already describes the basic communication pattern of -**commands**, **responses**, **events** and **announcements**. +### Communication pattern + +The typical communication pattern when you interact with a digital twin or a device uses correlated protocol messages. Each message contains a `correlation-id` header that links related messages together. + +The [Signals](basic-signals.html#communication-pattern-summary) chapter describes the basic communication pattern of **commands**, **responses**, **events**, and **announcements**. + +Here is the typical flow: + +1. You send a **command** (e.g., modify a thing attribute) with a `correlation-id`. +2. Ditto processes the command and returns a **response** with the same `correlation-id`, indicating success or failure. +3. If the command modified state, Ditto emits an **event** that subscribers receive. + +### Why a custom protocol? + +Ditto needs a protocol that: + +- Works across multiple transport layers (WebSocket, AMQP, MQTT, HTTP, Kafka) +- Supports both twin (server-side) and live (device-side) channels +- Enables fine-grained authorization on every message +- Carries structured metadata (topic, headers, path, value) alongside payloads + +Standard REST or pub/sub patterns alone do not cover all of these requirements. The Ditto Protocol bridges that gap with a single, consistent JSON envelope format. + +## Further reading + +- [Protocol specification](protocol-specification.html) -- the full message format reference +- [Twin and live channels](protocol-twinlive.html) -- how Ditto routes messages to twins vs. devices +- [Protocol bindings](protocol-bindings.html) -- how the protocol maps to specific transports +- [Protocol examples](protocol-examples.html) -- concrete message examples diff --git a/documentation/src/main/resources/pages/ditto/protocol-specification-acks.md b/documentation/src/main/resources/pages/ditto/protocol-specification-acks.md index 29c5db62701..a34c4338ead 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-specification-acks.md +++ b/documentation/src/main/resources/pages/ditto/protocol-specification-acks.md @@ -5,41 +5,52 @@ tags: [protocol] permalink: protocol-specification-acks.html --- +The Ditto Protocol represents acknowledgements in two forms: individual acknowledgements for a specific label, and aggregated acknowledgements that combine multiple results. -Ditto Protocol messages of [acknowledgements](basic-acknowledgements.html) come in 2 different -[protocol topic](protocol-specification-topic.html) variants. +{% include callout.html content="**TL;DR**: A single acknowledgement targets one label (e.g., `twin-persisted`) and carries a status code. Aggregated acknowledgements combine multiple acks into one message, with a combined status of `200` if all succeeded or `424` if any failed." type="primary" %} + +## Overview + +[Acknowledgements](basic-acknowledgements.html) come in two [protocol topic](protocol-specification-topic.html) variants, described below. ## Acknowledgement -An acknowledgment addressing a specific [acknowledgement label](basic-acknowledgements.html#acknowledgement-labels) -contains that label as last part of the topic: +A single acknowledgement addresses one specific [acknowledgement label](basic-acknowledgements.html#acknowledgement-labels). The label appears as the last segment of the topic: + ``` //things//acks/ ``` -The Ditto Protocol representation of an `Acknowledgement` is specified as follows: +The Ditto Protocol representation: {% include docson.html schema="jsonschema/protocol-ack.json" %} +## Acknowledgements (aggregated) -## Acknowledgements (aggregating) +An aggregated acknowledgement combines several single acknowledgements into one message. The topic omits the label: -An acknowledgment for aggregated structures contains several single acknowledgements as -its payload, and the topic is without a label: ``` //things//acks ``` -The Ditto Protocol representation of `Acknowledgements` is specified as follows: +The Ditto Protocol representation: {% include docson.html schema="jsonschema/protocol-acks.json" %} ### Combined status code -The status code of the aggregating acknowledgements is derived based on the status codes of the contained single acks. +Ditto derives the aggregated status code from the individual acks: + +- If only one acknowledgement is included, its status code is used. +- If multiple acknowledgements are included: + - **All successful** (status `200`-`299`): combined status is `200` (OK). + - **At least one failed** (status `>299`): combined status is `424` (Dependency failed). + +## Examples + +See the [acknowledgement examples](protocol-examples.html#acknowledgements-acks) for complete protocol message samples. -* if only one acknowledgement is included, this acknowledgment's status code is used -* if several acknowledgements are included: - * if all contained acknowledgements are successful (`200>=` HTTP status `<=299`), the overall status code is `200` (OK) - * if at least one acknowledgement failed (HTTP status `>299`), the overall status code is `424` (Dependency failed) +## Further reading +- [Acknowledgements and QoS](basic-acknowledgements.html) -- concepts, requesting, and issuing acks +- [Protocol specification](protocol-specification.html) -- the full message format reference diff --git a/documentation/src/main/resources/pages/ditto/protocol-specification-connections-announcement.md b/documentation/src/main/resources/pages/ditto/protocol-specification-connections-announcement.md index aa47bb4e21b..061bd636e00 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-specification-connections-announcement.md +++ b/documentation/src/main/resources/pages/ditto/protocol-specification-connections-announcement.md @@ -5,44 +5,4 @@ tags: [protocol] permalink: protocol-specification-connections-announcement.html --- -{% include note.html content="The *topic path* of connection announcements contains a *_* (underscore) as *namespace* -and no *channel* element. -See the [specification](protocol-specification-connections.html#ditto-protocol-topic-structure-for-connections) for -details. " %} - -## Connection announcements - -A connection announcement contains the announcement name as last part of the topic: -``` -_//connections/announcements/ -``` - -The Ditto Protocol representation of an `Announcement` is specified as follows: - -{% include docson.html schema="jsonschema/protocol-announcement.json" %} - -The following Connection announcements are currently supported: - -### ConnectionOpenedAnnouncement - -Announcement indicating that the connection is being opened. - -| Field | Value | -|-----------|-------------------------| -| **topic** | `_//connections/announcements/opened` | -| **path** | `/` | -| **value** | `JsonObject` containing
    * `openedAt` timestamp (as ISO-8601 `string`)| - -**Example:** [Announcement for connection opened](protocol-examples-connections-announcement-opened.html) - -### ConnectionClosedAnnouncement - -Announcement indicating that the connection is being closed gracefully. - -| Field | Value | -|-----------|-------------------------| -| **topic** | `_//connections/announcements/closed` | -| **path** | `/` | -| **value** | `JsonObject` containing
    * `closedAt` timestamp (as ISO-8601 `string`)| - -**Example:** [Announcement for connection closed](protocol-examples-connections-announcement-closed.html) +{% include note.html content="This content has been merged into the main [Connections protocol specification](protocol-specification-connections.html#announcements). Please refer to that page for the complete and up-to-date reference." %} diff --git a/documentation/src/main/resources/pages/ditto/protocol-specification-connections.md b/documentation/src/main/resources/pages/ditto/protocol-specification-connections.md index a83f4ea7c58..ad042d77b35 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-specification-connections.md +++ b/documentation/src/main/resources/pages/ditto/protocol-specification-connections.md @@ -5,21 +5,64 @@ tags: [protocol] permalink: protocol-specification-connections.html --- +The Connections specification defines the Ditto Protocol topic structure and announcements for managed connections. -## Ditto Protocol topic structure for connections +{% include callout.html content="**TL;DR**: Connection announcements use the topic pattern `_/connectionId/connections/announcements/subject`. Ditto publishes `opened` and `closed` announcements when a connection's state changes." type="primary" %} -A valid topic consists of four elements, describing the connection affected by this message and the type of the message: +## Topic structure for Connections + +A valid topic consists of these elements: ``` _//connections/announcements/ ``` -1. `_`: empty field. +1. `_`: empty namespace placeholder (connections have no namespace). 2. `connectionId`: the ID of the connection. -3. `group`: the group for addressing connections is `connections`. -4. `criterion`: the type of protocol messages addressing connection [announcements](basic-signals-announcement.html) is - [`announcements`](protocol-specification-policies-announcement.html). -5. `subject`: for [announcements](basic-signals-announcement.html) the `subject` contains the announcement name - -{% include note.html content="The topic path of the *connections* group does not contain a namespace since there is +3. `group`: always `connections`. +4. `criterion`: `announcements`. +5. `subject`: the announcement name. + +{% include note.html content="The topic path of the *connections* group does not contain a namespace since there is no correlation between namespaces and connections. Also there is no channel unlike the *things* group." %} + +## Announcements + +A connection announcement contains the announcement name as the last segment of the topic: + +``` +_//connections/announcements/ +``` + +The Ditto Protocol representation: + +{% include docson.html schema="jsonschema/protocol-announcement.json" %} + +### ConnectionOpenedAnnouncement + +Ditto publishes this announcement when a connection is being opened. + +| Field | Value | +|---|---| +| **topic** | `_//connections/announcements/opened` | +| **path** | `/` | +| **value** | JSON object with `openedAt` (ISO-8601 timestamp). | + +**Example:** [Connection opened announcement](protocol-examples-connections-announcement-opened.html) + +### ConnectionClosedAnnouncement + +Ditto publishes this announcement when a connection is being closed gracefully. + +| Field | Value | +|---|---| +| **topic** | `_//connections/announcements/closed` | +| **path** | `/` | +| **value** | JSON object with `closedAt` (ISO-8601 timestamp). | + +**Example:** [Connection closed announcement](protocol-examples-connections-announcement-closed.html) + +## Further reading + +- [Connections](basic-connections.html) -- connection concepts and configuration +- [Protocol specification](protocol-specification.html) -- the full message format reference diff --git a/documentation/src/main/resources/pages/ditto/protocol-specification-errors.md b/documentation/src/main/resources/pages/ditto/protocol-specification-errors.md index fc1faa66a5b..36f2ecf466d 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-specification-errors.md +++ b/documentation/src/main/resources/pages/ditto/protocol-specification-errors.md @@ -5,18 +5,41 @@ tags: [protocol] permalink: protocol-specification-errors.html --- +When a command fails, Ditto returns an error response containing details about what went wrong. -Ditto Protocol messages of [error responses](basic-signals-errorresponse.html) transport information about encountered -[errors](basic-errors.html), e.g. client errors or server errors: +{% include callout.html content="**TL;DR**: Error responses follow the standard Ditto Protocol envelope format and include an HTTP-semantics status code, an error code string, a human-readable message, and an optional description with resolution hints." type="primary" %} + +## Overview + +Ditto Protocol [error responses](basic-signals-errorresponse.html) transport information about [errors](basic-errors.html) caused by client mistakes or server problems. + +## Error response format {% include docson.html schema="jsonschema/protocol-error_response.json" %} -The **error** codes Ditto provides in addition to the HTTP **status** code -(e.g. error codes like "things:thing.tooLarge") is not to be considered as API and must therefore not be relied on. -It might change without prior notice. +Each error response contains: + +- **status**: An HTTP status code (e.g., `400`, `404`, `500`) +- **error**: A string error code (e.g., `things:thing.notfound`) +- **message**: A short description of the problem +- **description**: An optional hint on how to resolve the error + +## How it works + +When Ditto cannot process a command, it returns an error response instead of a success response. You correlate the error with the original command using the `correlation-id` header. + +The error response topic differs from the command topic. For example, a failed `modify` command may return an error with a topic ending in `errors` rather than `commands/modify`. + +## Error codes + +The `error` string codes that Ditto provides (e.g., `things:thing.tooLarge`) are **not** part of the stable API. They may change without prior notice. Use the HTTP `status` code to identify and handle errors programmatically. ## Examples -Examples for error responses can be found here: * [Things error response examples](protocol-examples-errorresponses.html) * [Policies error response examples](protocol-examples-policies-errorresponses.html) + +## Further reading + +- [Errors](basic-errors.html) -- the error model and common error codes +- [Protocol specification](protocol-specification.html) -- the full message format reference diff --git a/documentation/src/main/resources/pages/ditto/protocol-specification-policies-announcement.md b/documentation/src/main/resources/pages/ditto/protocol-specification-policies-announcement.md index c4fab0e7cd3..12fc77f2d1a 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-specification-policies-announcement.md +++ b/documentation/src/main/resources/pages/ditto/protocol-specification-policies-announcement.md @@ -5,30 +5,4 @@ tags: [protocol] permalink: protocol-specification-policies-announcement.html --- -{% include note.html content="The *topic path* of policy commands contains no *channel* element. -See the [specification](protocol-specification-policies.html#ditto-protocol-topic-structure-for-policies) for details. " %} - -## Policy announcements - -A Policy announcement contains the announcement name as last part of the topic: -``` -//policies/announcements/ -``` - -The Ditto Protocol representation of an `Announcement` is specified as follows: - -{% include docson.html schema="jsonschema/protocol-announcement.json" %} - -The following Policy announcements are currently supported: - -### SubjectDeletionAnnouncement - -Announcement indicating that some subjects of a policy are deleted or about to be deleted soon. - -| Field | Value | -|-----------|-------------------------| -| **topic** | `//policies/announcements/subjectDeletion` | -| **path** | `/` | -| **value** | `JsonObject` containing
    * `deleteAt` timestamp (as ISO-8601 `string`)
    * `subjectIds` of policy [subjects](basic-policy.html#subjects) which will soon be deleted (`JsonArray` of subjects `string`s)| - -**Example:** [Announcement for subject deletion](protocol-examples-policies-announcement-subjectDeletion.html) +{% include note.html content="This content has been merged into the main [Policies protocol specification](protocol-specification-policies.html#announcements). Please refer to that page for the complete and up-to-date reference." %} diff --git a/documentation/src/main/resources/pages/ditto/protocol-specification-policies-create-or-modify.md b/documentation/src/main/resources/pages/ditto/protocol-specification-policies-create-or-modify.md index 087f0c15cc9..a4e01399d6c 100644 --- a/documentation/src/main/resources/pages/ditto/protocol-specification-policies-create-or-modify.md +++ b/documentation/src/main/resources/pages/ditto/protocol-specification-policies-create-or-modify.md @@ -5,277 +5,4 @@ tags: [protocol] permalink: protocol-specification-policies-create-or-modify.html --- -## Create a Policy - -Create a Policy with the ID specified by the `/` pair in the topic and the -JSON representation provided in the `value`. - -### Command - -| Field | Value | -|-----------|-------------------------| -| **topic** | `//policies/commands/create` | -| **path** | `/` | -| **value** | The complete Policy as JSON object, see [Policy representation (JSON)](protocol-specification-policies.html#policy-representation). | - -### Response - -| Field | | Value | -|------------|--------|--------------------------| -| **topic** | | `//policies/commands/create` | -| **path** | | `/` | -| **value** | | The created Policy as JSON object, see [Policy representation (JSON)](protocol-specification-policies.html#policy-representation). | -| **status** | *code* | | -| | `201` | Success - The Policy was successfully created. | - -**Example:** [Create a Policy](protocol-examples-policies-createpolicy.html). - -## Create or modify a Policy - -This command modifies the Policy with the ID specified by the `/` pair in the `topic` and with -the JSON provided in the `value`, if it already exists. Otherwise, the Policy is created. - -### Command - -| Field | Value | -|-----------|-------------------------| -| **topic** | `//policies/commands/modify` | -| **path** | `/` | -| **value** | The complete Policy as JSON.
    see [Policy representation (JSON)](protocol-specification-policies.html#policy-representation) | - -For modifying an existing policy, the authorized subject needs WRITE permission on the `policy:/.` resource.
    -If the Policy does not yet exist, the same rules apply as described for the [create command](#create-a-policy). - -### Response - -| Field | | Value | -|------------|--------|--------------------------| -| **topic** | | `//policies/commands/modify` | -| **path** | | `/` | -| **value** | | The created Policy as JSON object, see [Policy representation (JSON)](protocol-specification-policies.html#policy-representation). This field is not available, if the Policy entry already existed. | -| **status** | _code_ | -| | `201` | Success - The Policy was successfully created. | -| | `204` | Success - The Policy was successfully updated. | - -**Example:** [Modify a Policy](protocol-examples-policies-modifypolicy.html) - -## Modify Policy entries - -Modify the Policy entries of the Policy identified by the `/` pair in the `topic` field. - -### Command - -| Field | Value | -|-----------|-------------------------| -| **topic** | `//policies/commands/modify` | -| **path** | `/entries` | -| **value** | The Policy entries as JSON.
    see [Policy representation (JSON)](protocol-specification-policies.html#policy-representation) | - -### Response - -| Field | | Value | -|------------|--------|--------------------------| -| **topic** | | `//policies/commands/modify` | -| **path** | | `/entries` | -| **status** | _code_ | -| | `204` | Success - The Policy entries were successfully updated. | - -**Example:** [Modify all Policy entries](protocol-examples-policies-modifypolicyentries.html) - -## Create or modify a Policy entry - -Create or modify the Policy entry identified by the `/` pair in the `topic` field and the ` -