From 48ca94607dc6d2850e0c24921952ccd31836a3e2 Mon Sep 17 00:00:00 2001 From: Diogo Mendes Matsubara Date: Thu, 14 Nov 2024 12:09:38 +0100 Subject: [PATCH 1/4] fix: Don't tag release branch during dry-run Aligning with [ci repository](https://github.com/eclipse-zenoh/ci/pull/239) to not tag the repo when we are running release dry-run --- .github/workflows/release.yml | 1 + ci/scripts/bump-and-tag.bash | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8d09014..cb57c91d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,6 +60,7 @@ jobs: - name: Bump and tag project run: bash ci/scripts/bump-and-tag.bash env: + LIVE_RUN: ${{ inputs.live-run || false }} VERSION: ${{ steps.create-release-branch.outputs.version }} BUMP_DEPS_VERSION: ${{ inputs.zenoh-version }} BUMP_DEPS_PATTERN: ${{ inputs.zenoh-version && 'zenoh.*' || '' }} diff --git a/ci/scripts/bump-and-tag.bash b/ci/scripts/bump-and-tag.bash index f39f9bff..5b5f7ee4 100644 --- a/ci/scripts/bump-and-tag.bash +++ b/ci/scripts/bump-and-tag.bash @@ -2,6 +2,7 @@ set -xeo pipefail +readonly live_run=${LIVE_RUN:-false} # Release number readonly version=${VERSION:?input VERSION is required} # Dependencies' pattern @@ -59,7 +60,9 @@ if [[ "$bump_deps_pattern" != '' ]]; then fi fi -git tag --force "$version" -m "v$version" +if [[ ${live_run} ]]; then + git tag --force "$version" -m "v$version" +fi git log -10 git show-ref --tags git push origin From c58f7ef16dce2ae4f8fd908ba8b816da5b9f37d0 Mon Sep 17 00:00:00 2001 From: Diogo Matsubara Date: Wed, 18 Dec 2024 09:47:14 +0100 Subject: [PATCH 2/4] fix: exclude internal from automatic changelog (#159) Don't include PRs labeled as internal in the automatically generated changelog --- .github/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/release.yml b/.github/release.yml index 23d56cfb..92919932 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -32,4 +32,7 @@ changelog: - dependencies - title: Other changes labels: - - "*" \ No newline at end of file + - "*" + exclude: + labels: + - internal \ No newline at end of file From 9880420839ed09df7c477ffcc3a1a214922b4762 Mon Sep 17 00:00:00 2001 From: "ChenYing Kuo (CY)" Date: Wed, 18 Dec 2024 16:53:11 +0800 Subject: [PATCH 3/4] Add CI for checking markdown format. (#161) Signed-off-by: ChenYing Kuo --- .github/workflows/ci.yml | 11 +++++- .markdownlint.yaml | 7 ++++ README.md | 74 +++++++++++++++++++--------------------- examples/README.md | 6 ++-- 4 files changed, 56 insertions(+), 42 deletions(-) create mode 100644 .markdownlint.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0e82042..fabf2b74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,15 @@ jobs: - name: Gradle Test run: gradle jvmTest --info + markdown_lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DavidAnson/markdownlint-cli2-action@v18 + with: + config: '.markdownlint.yaml' + globs: '**/README.md' + # NOTE: In GitHub repository settings, the "Require status checks to pass # before merging" branch protection rule ensures that commits are only merged # from branches where specific status checks have passed. These checks are @@ -67,7 +76,7 @@ jobs: ci: name: CI status checks runs-on: ubuntu-latest - needs: build + needs: [build, markdown_lint] if: always() steps: - name: Check whether all jobs pass diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..144a0959 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,7 @@ +{ + "MD013": false, # Line length limitation + "MD024": false, # Allow multiple headings with the same content + "MD033": false, # Enable Inline HTML + "MD041": false, # Allow first line heading + "MD045": false, # Allow Images have no alternate text +} \ No newline at end of file diff --git a/README.md b/README.md index 1e8c711e..6d76814e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ [![License](https://img.shields.io/badge/License-EPL%202.0-blue)](https://choosealicense.com/licenses/epl-2.0/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) - # Eclipse Zenoh The Eclipse Zenoh: Zero Overhead Pub/sub, Store/Query and Compute. @@ -16,29 +15,27 @@ Zenoh (pronounce _/zeno/_) unifies data in motion, data at rest and computations Check the website [zenoh.io](http://zenoh.io) and the [roadmap](https://github.com/eclipse-zenoh/roadmap) for more detailed information. - ---- # Java Java API - This repository provides a Java compatible Kotlin binding based on the main [Zenoh implementation written in Rust](https://github.com/eclipse-zenoh/zenoh). The code relies on the Zenoh JNI native library, which written in Rust and communicates with the Kotlin layer via the Java Native Interface (JNI). ## Documentation -The documentation of the API is published at https://eclipse-zenoh.github.io/zenoh-java/index.html. +The documentation of the API is published at [https://eclipse-zenoh.github.io/zenoh-java/index.html](https://eclipse-zenoh.github.io/zenoh-java/index.html). Alternatively, you can build it locally as [explained below](#building-the-documentation). ---- -# How to import +# How to import :warning: Note nº1: **Breaking changes incoming** -With the purpose of providing an upgraded API across all the languages supported by the Zenoh ecosystem, +With the purpose of providing an upgraded API across all the languages supported by the Zenoh ecosystem, we are currently releasing a series of alpha and beta versions, the latest one being currently the [1.0.0-beta.1](https://github.com/eclipse-zenoh/zenoh-java/releases/tag/1.0.0-beta.1). @@ -50,13 +47,13 @@ the time comes. :warning: Note nº2: **About the released packages** The released packages can be found in the "packages" section of this repository, or in the links below: -* [Android packages](https://github.com/eclipse-zenoh/zenoh-java/packages/2019754/versions) -* [JVM packages](https://github.com/eclipse-zenoh/zenoh-java/packages/2017294/versions) + +- [Android packages](https://github.com/eclipse-zenoh/zenoh-java/packages/2019754/versions) +- [JVM packages](https://github.com/eclipse-zenoh/zenoh-java/packages/2017294/versions) These are 'nightly' packages that need to be imported as explained in the following sections of this README. We plan to publish our packages into Maven central for the `1.0.0` stable release. - ## Android For Android applications For this first version we have published a [Github package](https://github.com/eclipse-zenoh/zenoh-java/packages/2019754) with the library which can be imported on your projects. @@ -95,6 +92,7 @@ implementation("io.zenoh:zenoh-java-android:0.11.0") ### Platforms The library targets the following platforms: + - x86 - x86_64 - arm @@ -150,26 +148,27 @@ implementation("io.zenoh:zenoh-java-jvm:0.11.0") For the moment, the library targets the following platforms: - - x86_64-unknown-linux-gnu - - aarch64-unknown-linux-gnu - - x86_64-apple-darwin - - aarch64-apple-darwin - - x86_64-pc-windows-msvc +- x86_64-unknown-linux-gnu +- aarch64-unknown-linux-gnu +- x86_64-apple-darwin +- aarch64-apple-darwin +- x86_64-pc-windows-msvc - ---- +---- # How to build it ## What you need Basically: -* Rust ([Installation guide](https://doc.rust-lang.org/cargo/getting-started/installation.html)) -* Kotlin ([Installation guide](https://kotlinlang.org/docs/getting-started.html#backend)) -* Gradle ([Installation guide](https://gradle.org/install/)) + +- Rust ([Installation guide](https://doc.rust-lang.org/cargo/getting-started/installation.html)) +- Kotlin ([Installation guide](https://kotlinlang.org/docs/getting-started.html#backend)) +- Gradle ([Installation guide](https://gradle.org/install/)) and in case of targetting Android you'll also need: -* Android SDK ([Installation guide](https://developer.android.com/about/versions/11/setup-sdk)) + +- Android SDK ([Installation guide](https://developer.android.com/about/versions/11/setup-sdk)) ## JVM JVM @@ -189,7 +188,7 @@ Once we have published the package, we should be able to find it under `~/.m2/re Finally, in the `build.gradle.kts` file of the project where you intend to use this library, add mavenLocal to the list of repositories and add zenoh-java as a dependency: -``` +```kotlin repositories { mavenCentral() mavenLocal() @@ -212,14 +211,14 @@ It can be set up by using Android Studio (go to `Preferences > Languages & Frame or alternatively it can be found [here](https://developer.android.com/ndk/downloads). The native platforms we are going to target are the following ones: -``` + - x86 - x86_64 - arm - arm64 -``` Therefore, if they are not yet already added to the Rust toolchain, run: + ```bash rustup target add armv7-linux-androideabi; \ rustup target add i686-linux-android; \ @@ -229,8 +228,8 @@ rustup target add x86_64-linux-android to install them. - So, in order to publish the library onto Maven Local, run: + ```bash gradle -Pandroid=true publishAndroidReleasePublicationToMavenLocal ``` @@ -240,7 +239,8 @@ publish the library, containing the native binaries. You should now be able to see the package under `~/.m2/repository/io/zenoh/zenoh-java-android/0.11.0` with the following files: -``` + +```raw zenoh-java-android-0.11.0-sources.jar zenoh-java-android-0.11.0.aar zenoh-java-android-0.11.0.module @@ -251,7 +251,8 @@ Now the library is published on maven local, let's now see how to import it into First, we need to indicate we want to look into mavenLocal for our library, so in your top level `build.gradle.kts` you need to specify the `mavenLocal` repository: -``` + +```kotlin repositories { mavenCentral() ... @@ -260,13 +261,14 @@ repositories { ``` Then in your app's `build.gradle.kts` filen add the dependency: -``` + +```kotlin implementation("io.zenoh:zenoh-java-android:0.11.0") ``` And finally, do not forget to add the required internet permissions on your manifest! -``` +```xml ``` @@ -278,6 +280,7 @@ And that was it! You can now import the code from the `io.zenoh` package and use Because it's a Kotlin project, we use [Dokka](https://kotlinlang.org/docs/dokka-introduction.html) to generate the documentation. In order to build it, run: + ```bash gradle zenoh-java:dokkaHtml ``` @@ -308,7 +311,7 @@ causes the logs to appear in standard output. The log levels are the ones from Rust: `trace`, `info`, `debug`, `error` and `warn`. ---- +---- # Examples @@ -323,25 +326,20 @@ For instance in order to run the [ZPub](examples/src/main/java/io.zenoh/ZPub.jav You can find more info about these examples on the [examples README file](/examples/README.md). - - - - ---- # :warning: Considerations & Future work -### Packaging +## Packaging We intend to publish this code on Maven in the short term in order to ease the installation, but for the moment, until we add some extra functionalities and test this library a bit further, we will only publish packages to Github packages. -### Potential API changes - -When using this library, keep in mind the api is not fully stable. Changes are to be expected, especially for version 1.0.0. +## Potential API changes +When using this library, keep in mind the api is not fully stable. Changes are to be expected, especially for version 1.0.0. -### Performance +## Performance The communication between the Kotlin code and the Rust code through the java native interface (JNI) has its toll on performance. diff --git a/examples/README.md b/examples/README.md index 21c2c398..886ebc34 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,8 +4,6 @@ ## Start instructions - - ```bash ./gradle ``` @@ -13,7 +11,7 @@ :warning: Passing arguments to these examples has not been enabled yet for this first version. Altering the Zenoh configuration for these examples must be done programmatically. :warning: ----- +---- ## Examples description @@ -29,6 +27,7 @@ Usage: ``` ### ZSub + Creates a subscriber with a key expression. The subscriber will be notified of each put made on any key expression matching the subscriber's key expression, and will print this notification. @@ -61,6 +60,7 @@ Usage: ``` ### ZDelete + Performs a Delete operation into a path/value into Zenoh. Usage: From 30af980e42948bc3b00a9f0991ce5eb54583e74c Mon Sep 17 00:00:00 2001 From: Darius Maitia Date: Thu, 19 Dec 2024 08:00:43 -0300 Subject: [PATCH 4/4] API Alignment (#157) * Alignment: bytes package * Alignment: encoding update * Alignment: config package * Alignment: replacing all exceptions with ZError * Alignment: ext package * Alignment: jni callbacks * Alignment: config * Alignment: JNIKeyExpr * Alignment: JNIPublisher * Alignment: JNIQuery * Alignment: JNIScout * Alignment: JNISession * Alignment JNIZBytes & JNIZenohId * Alignment: KeyExpr & SetIntersectionLevel * Alignment: pubsub package * Alignment: Publisher & qos package * Alignment: query package - wip * Alignment: adding scouting package * Alignment: removing Value * Alignment: logger * Alignmment: Zenoh.kt - wip * Alignment: publisher - adding encoding and reliability to builder * Alignment: subscriber * Alignment: sample * Alignment: query & reply - wip * Alignment: Put & Session * Alignment: fix 'IntoSelector' * Alignment: updating zenoh-jni * Alignment: wip - converting tests to java tests. Added config tests. * Alignment: wip - converting tests to java tests. Added delete test. * Alignment: wip - converting tests to java tests. Added encoding tests. * Alignment: wip - converting tests to java tests. Added get tests. * Alignment: wip - converting tests to java tests. Added key expr tests. * Alignment: wip - converting tests to java tests. Added parameters tests. * Alignment: wip - converting tests to java tests. Added publisher tests. * Alignment: wip - converting tests to java tests. Added Queryable tests. * Alignment: wip - converting tests to java tests. Added Put tests. * Alignment: wip - Added Scouting tests + adding scouting builder. * Alignment: wrapping up scouting, closing queue upon scout close. * Alignment: adding selector tests * Alignment: fix SessionInfo + adding tests * Alignment: fix Scouting queue test * amend! Alignment: fix SessionInfo + adding tests Alignment: fix SessionInfo + adding tests * Alignment: session tests * Alignment: user attachment tests * Alignment: subscriber tests * Alignment: removing the zenoh-ext package (to be added later in another PR) * Alignment: Publisher config params * Alignment: Subscriber config params * Alignment: Queryable config params * Alignment: Subscriber config params refactor * Alignment: Queryable declaration and Query.reply config params. * Alignment: Get config params * Alignment: Subscriber config params refactor * Fix config test * Alignment - Scouting config params * Alignment: adding Liveliness * Gitignore update * Alignment: fix logging * Alignment: publisher put and delete config param * Alignment: examples - adding picocli for CLI args * Alignment: examples - adding missing examples * Alignment: examples - adding ping and pong examples * Alignment: examples refactor + refactor queryable config logic * Alignment: fix publisher put encoding fallback * Alignment: removing SubscriberConfig.kt * Alignment: renaming PublisherConfig to PublisherOptions * Alignment: renaming DeleteConfig to DeleteOptions and removing 'builder' functions in it. * Alignment: renaming GetConfig to GetOptions and removing 'builder' functions in it. * Alignment: renaming PutConfig to PutOptions and removing 'builder' functions in it. * Alignment: renaming ReplyConfig to ReplyOptions and removing 'builder' functions in it. * Alignment: renaming ReplyDelConfig to ReplyDelOptions and removing 'builder' functions in it. * Alignment: renaming ReplyErrConfig to ReplyErrOptions and removing 'builder' functions in it. * Alignment: renaming QueryableConfig to QueryableOptions and removing 'builder' functions in it. * Alignment: renaming ScoutConfig to ScoutOptions and removing 'builder' functions in it. * Alignment: renaming Liveliness.SubscriberConfig + renaming variables * Alignment: queryable options refactor * Alignment: removing Resolvable * Alignment: splitting Queryable, Subscriber and Get into Handler and Callback subclasses + tidying up documentation. * Alignment: removing JNIZBytes * Alignment: removing unused kotlin json dependency * Alignment: options refactor on QoS param --- .gitignore | 1 + examples/build.gradle.kts | 10 + examples/src/main/java/io/zenoh/Config.kt | 74 ++ .../src/main/java/io/zenoh/QueueHandler.java | 33 + examples/src/main/java/io/zenoh/ZDelete.java | 88 ++- examples/src/main/java/io/zenoh/ZGet.java | 198 ++++- .../main/java/io/zenoh/ZGetLiveliness.java | 162 ++++ examples/src/main/java/io/zenoh/ZInfo.java | 101 +++ .../src/main/java/io/zenoh/ZLiveliness.java | 113 +++ examples/src/main/java/io/zenoh/ZPing.java | 167 +++++ examples/src/main/java/io/zenoh/ZPong.java | 127 ++++ examples/src/main/java/io/zenoh/ZPub.java | 143 +++- examples/src/main/java/io/zenoh/ZPubThr.java | 146 +++- examples/src/main/java/io/zenoh/ZPut.java | 120 ++- .../src/main/java/io/zenoh/ZQueryable.java | 169 ++++- examples/src/main/java/io/zenoh/ZScout.java | 67 ++ examples/src/main/java/io/zenoh/ZSub.java | 154 +++- .../main/java/io/zenoh/ZSubLiveliness.java | 181 +++++ examples/src/main/java/io/zenoh/ZSubThr.java | 167 ++++- zenoh-java/build.gradle.kts | 2 +- .../src/androidMain/kotlin/io.zenoh/Zenoh.kt | 18 +- .../src/commonMain/kotlin/io/zenoh/Config.kt | 132 +++- .../src/commonMain/kotlin/io/zenoh/Logger.kt | 21 +- .../src/commonMain/kotlin/io/zenoh/Session.kt | 636 ++++++++++------ .../src/commonMain/kotlin/io/zenoh/Zenoh.kt | 129 +++- .../commonMain/kotlin/io/zenoh/ZenohType.kt | 3 +- .../kotlin/io/zenoh/bytes/Encoding.kt | 502 +++++++++++++ .../kotlin/io/zenoh/bytes/IntoZBytes.kt | 42 ++ .../kotlin/io/zenoh/bytes/ZBytes.kt | 69 ++ .../SessionException.kt => config/WhatAmI.kt} | 16 +- .../kotlin/io/zenoh/config/ZenohId.kt | 40 + .../io/zenoh/exceptions/KeyExprException.kt | 24 - .../{ZenohException.kt => ZError.kt} | 4 +- .../io/zenoh/handlers/BlockingQueueHandler.kt | 6 +- .../kotlin/io/zenoh/handlers/Handler.kt | 9 +- .../kotlin/io/zenoh/jni/JNIConfig.kt | 101 +++ .../kotlin/io/zenoh/jni/JNIKeyExpr.kt | 55 +- .../kotlin/io/zenoh/jni/JNILiveliness.kt | 186 +++++ .../kotlin/io/zenoh/jni/JNILivelinessToken.kt | 12 + .../kotlin/io/zenoh/jni/JNIPublisher.kt | 27 +- .../kotlin/io/zenoh/jni/JNIQuery.kt | 32 +- .../kotlin/io/zenoh/jni/JNIQueryable.kt | 2 +- .../kotlin/io/zenoh/jni/JNIScout.kt | 87 +++ .../kotlin/io/zenoh/jni/JNISession.kt | 428 +++++++---- .../kotlin/io/zenoh/jni/JNISubscriber.kt | 2 +- .../{Resolvable.kt => jni/JNIZenohId.kt} | 16 +- .../io/zenoh/jni/callbacks/JNIGetCallback.kt | 2 +- .../jni/callbacks/JNIQueryableCallback.kt | 17 +- .../callbacks/JNIScoutCallback.kt} | 10 +- .../kotlin/io/zenoh/keyexpr/KeyExpr.kt | 66 +- ...IntoKeyExpr.kt => SetIntersectionLevel.kt} | 19 +- .../kotlin/io/zenoh/liveliness/Liveliness.kt | 198 +++++ .../io/zenoh/liveliness/LivelinessToken.kt | 53 ++ .../kotlin/io/zenoh/prelude/Encoding.kt | 119 --- .../commonMain/kotlin/io/zenoh/prelude/QoS.kt | 68 -- .../kotlin/io/zenoh/publication/Delete.kt | 104 --- .../kotlin/io/zenoh/publication/Publisher.kt | 170 ----- .../kotlin/io/zenoh/publication/Put.kt | 117 --- .../kotlin/io/zenoh/pubsub/DeleteOptions.kt | 38 + .../kotlin/io/zenoh/pubsub/Publisher.kt | 120 +++ .../io/zenoh/pubsub/PublisherOptions.kt | 33 + .../kotlin/io/zenoh/pubsub/PutOptions.kt | 38 + .../kotlin/io/zenoh/pubsub/Subscriber.kt | 122 +++ .../{prelude => qos}/CongestionControl.kt | 4 +- .../io/zenoh/{prelude => qos}/Priority.kt | 5 +- .../src/commonMain/kotlin/io/zenoh/qos/QoS.kt | 33 + .../zenoh/{subscriber => qos}/Reliability.kt | 2 +- .../io/zenoh/query/ConsolidationMode.kt | 4 - .../commonMain/kotlin/io/zenoh/query/Get.kt | 188 +---- .../kotlin/io/zenoh/query/Parameters.kt | 148 ++++ .../commonMain/kotlin/io/zenoh/query/Query.kt | 123 +++ .../kotlin/io/zenoh/query/Queryable.kt | 127 ++++ .../commonMain/kotlin/io/zenoh/query/Reply.kt | 280 ++----- .../kotlin/io/zenoh/query/Selector.kt | 103 +++ .../kotlin/io/zenoh/queryable/Query.kt | 99 --- .../kotlin/io/zenoh/queryable/Queryable.kt | 176 ----- .../kotlin/io/zenoh/sample/Sample.kt | 47 +- .../zenoh/{prelude => sample}/SampleKind.kt | 2 +- .../kotlin/io/zenoh/scouting/Hello.kt | 29 + .../kotlin/io/zenoh/scouting/Scout.kt | 109 +++ .../ScoutOptions.kt} | 16 +- .../kotlin/io/zenoh/selector/IntoSelector.kt | 30 - .../kotlin/io/zenoh/selector/Selector.kt | 65 -- .../zenoh/{ => session}/SessionDeclaration.kt | 9 +- .../kotlin/io/zenoh/session/SessionInfo.kt | 49 ++ .../kotlin/io/zenoh/subscriber/Subscriber.kt | 184 ----- .../commonMain/kotlin/io/zenoh/value/Value.kt | 66 -- .../commonTest/kotlin/io/zenoh/DeleteTest.kt | 39 - .../kotlin/io/zenoh/EncodingTest.kt | 168 ----- .../src/commonTest/kotlin/io/zenoh/GetTest.kt | 121 --- .../commonTest/kotlin/io/zenoh/KeyExprTest.kt | 126 ---- .../kotlin/io/zenoh/PublisherTest.kt | 77 -- .../src/commonTest/kotlin/io/zenoh/PutTest.kt | 47 -- .../kotlin/io/zenoh/QueryableTest.kt | 241 ------ .../kotlin/io/zenoh/SelectorTest.kt | 30 - .../commonTest/kotlin/io/zenoh/SessionTest.kt | 65 -- .../kotlin/io/zenoh/SubscriberTest.kt | 150 ---- .../src/jvmMain/kotlin/io/zenoh/Zenoh.kt | 225 +++--- .../src/jvmTest/java/io/zenoh/ConfigTest.java | 352 +++++++++ .../src/jvmTest/java/io/zenoh/DeleteTest.java | 47 ++ .../jvmTest/java/io/zenoh/EncodingTest.java | 217 ++++++ .../src/jvmTest/java/io/zenoh/GetTest.java | 125 ++++ .../jvmTest/java/io/zenoh/KeyExprTest.java | 166 ++++ .../jvmTest/java/io/zenoh/LivelinessTest.java | 70 ++ .../jvmTest/java/io/zenoh/ParametersTest.java | 125 ++++ .../jvmTest/java/io/zenoh/PublisherTest.java | 99 +++ .../src/jvmTest/java/io/zenoh/PutTest.java | 54 ++ .../jvmTest/java/io/zenoh/QueryableTest.java | 267 +++++++ .../src/jvmTest/java/io/zenoh/ScoutTest.java | 90 +++ .../jvmTest/java/io/zenoh/SelectorTest.java | 50 ++ .../java/io/zenoh/SessionInfoTest.java | 111 +++ .../jvmTest/java/io/zenoh/SessionTest.java | 72 ++ .../jvmTest/java/io/zenoh/SubscriberTest.java | 155 ++++ .../java/io/zenoh/UserAttachmentTest.java | 219 ++++++ zenoh-jni/Cargo.lock | 706 +++++++++--------- zenoh-jni/Cargo.toml | 10 +- zenoh-jni/src/config.rs | 185 +++++ zenoh-jni/src/errors.rs | 63 +- zenoh-jni/src/key_expr.rs | 161 +++- zenoh-jni/src/lib.rs | 4 + zenoh-jni/src/liveliness.rs | 242 ++++++ zenoh-jni/src/logger.rs | 28 +- zenoh-jni/src/publisher.rs | 13 +- zenoh-jni/src/query.rs | 22 +- zenoh-jni/src/queryable.rs | 2 +- zenoh-jni/src/scouting.rs | 113 +++ zenoh-jni/src/session.rs | 388 ++++++---- zenoh-jni/src/utils.rs | 72 +- zenoh-jni/src/zenoh_id.rs | 42 ++ 129 files changed, 9393 insertions(+), 4180 deletions(-) create mode 100644 examples/src/main/java/io/zenoh/Config.kt create mode 100644 examples/src/main/java/io/zenoh/QueueHandler.java create mode 100644 examples/src/main/java/io/zenoh/ZGetLiveliness.java create mode 100644 examples/src/main/java/io/zenoh/ZInfo.java create mode 100644 examples/src/main/java/io/zenoh/ZLiveliness.java create mode 100644 examples/src/main/java/io/zenoh/ZPing.java create mode 100644 examples/src/main/java/io/zenoh/ZPong.java create mode 100644 examples/src/main/java/io/zenoh/ZScout.java create mode 100644 examples/src/main/java/io/zenoh/ZSubLiveliness.java create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/IntoZBytes.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt rename zenoh-java/src/commonMain/kotlin/io/zenoh/{exceptions/SessionException.kt => config/WhatAmI.kt} (62%) create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/KeyExprException.kt rename zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/{ZenohException.kt => ZError.kt} (82%) create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt rename zenoh-java/src/commonMain/kotlin/io/zenoh/{Resolvable.kt => jni/JNIZenohId.kt} (73%) rename zenoh-java/src/commonMain/kotlin/io/zenoh/{protocol/ZenohID.kt => jni/callbacks/JNIScoutCallback.kt} (76%) rename zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/{IntoKeyExpr.kt => SetIntersectionLevel.kt} (56%) create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Encoding.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/QoS.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Delete.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Publisher.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Put.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/DeleteOptions.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PublisherOptions.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PutOptions.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt rename zenoh-java/src/commonMain/kotlin/io/zenoh/{prelude => qos}/CongestionControl.kt (94%) rename zenoh-java/src/commonMain/kotlin/io/zenoh/{prelude => qos}/Priority.kt (94%) create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/qos/QoS.kt rename zenoh-java/src/commonMain/kotlin/io/zenoh/{subscriber => qos}/Reliability.kt (97%) create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Query.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Queryable.kt rename zenoh-java/src/commonMain/kotlin/io/zenoh/{prelude => sample}/SampleKind.kt (96%) create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Hello.kt create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt rename zenoh-java/src/commonMain/kotlin/io/zenoh/{exceptions/JNIException.kt => scouting/ScoutOptions.kt} (60%) delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/selector/IntoSelector.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/selector/Selector.kt rename zenoh-java/src/commonMain/kotlin/io/zenoh/{ => session}/SessionDeclaration.kt (69%) create mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionInfo.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Subscriber.kt delete mode 100644 zenoh-java/src/commonMain/kotlin/io/zenoh/value/Value.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/DeleteTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/EncodingTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/GetTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/PublisherTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/PutTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/QueryableTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/SelectorTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/SessionTest.kt delete mode 100644 zenoh-java/src/commonTest/kotlin/io/zenoh/SubscriberTest.kt create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/ConfigTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/DeleteTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/EncodingTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/GetTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/KeyExprTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/LivelinessTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/ParametersTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/PublisherTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/PutTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/ScoutTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/SelectorTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/SessionInfoTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java create mode 100644 zenoh-java/src/jvmTest/java/io/zenoh/UserAttachmentTest.java create mode 100644 zenoh-jni/src/config.rs create mode 100644 zenoh-jni/src/liveliness.rs create mode 100644 zenoh-jni/src/scouting.rs create mode 100644 zenoh-jni/src/zenoh_id.rs diff --git a/.gitignore b/.gitignore index 86cb1e5f..53d109f6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ /examples/build/ /build/ /.gradle/ +*/build/ diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 6afd9c21..683709d3 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -14,6 +14,7 @@ plugins { kotlin("jvm") + kotlin("plugin.serialization") version "1.9.0" } kotlin { @@ -23,17 +24,26 @@ kotlin { dependencies { implementation(project(":zenoh-java")) implementation("commons-net:commons-net:3.9.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") + implementation("info.picocli:picocli:4.7.4") } tasks { val examples = listOf( "ZDelete", "ZGet", + "ZGetLiveliness", + "ZInfo", + "ZLiveliness", + "ZPing", + "ZPong", "ZPub", "ZPubThr", "ZPut", "ZQueryable", + "ZScout", "ZSub", + "ZSubLiveliness", "ZSubThr" ) diff --git a/examples/src/main/java/io/zenoh/Config.kt b/examples/src/main/java/io/zenoh/Config.kt new file mode 100644 index 00000000..c8102728 --- /dev/null +++ b/examples/src/main/java/io/zenoh/Config.kt @@ -0,0 +1,74 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh + +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlin.io.path.Path + +@Serializable +data class ConfigData( + @SerialName("connect") var connect: Connect? = null, + @SerialName("listen") var listen: Listen? = null, + @SerialName("mode") var mode: String? = null, + @SerialName("scouting") var scouting: Scouting? = null, +) + +@Serializable +data class Connect( + @SerialName("endpoints") var endpoints: List +) + +@Serializable +data class Listen( + @SerialName("endpoints") var endpoints: List +) + +@Serializable +data class Scouting( + @SerialName("multicast") var multicast: Multicast, +) + +@Serializable +data class Multicast( + @SerialName("enabled") var enabled: Boolean, +) + +internal fun loadConfig( + emptyArgs: Boolean, + configFile: String?, + connectEndpoints: List?, + listenEndpoints: List?, + noMulticastScouting: Boolean?, + mode: String? +): Config { + return if (emptyArgs) { + Config.loadDefault() + } else { + configFile?.let { + Config.fromFile(path = Path(it)) + } ?: run { + val connect = connectEndpoints?.let {Connect(it)} + val listen = listenEndpoints?.let {Listen(it)} + val scouting = noMulticastScouting?.let { Scouting(Multicast(!it)) } + val configData = ConfigData(connect, listen, mode, scouting) + val jsonConfig = Json.encodeToJsonElement(configData).toString() + println(jsonConfig) + Config.fromJson(jsonConfig) + } + } +} diff --git a/examples/src/main/java/io/zenoh/QueueHandler.java b/examples/src/main/java/io/zenoh/QueueHandler.java new file mode 100644 index 00000000..cf56fbf4 --- /dev/null +++ b/examples/src/main/java/io/zenoh/QueueHandler.java @@ -0,0 +1,33 @@ +package io.zenoh; + +import io.zenoh.handlers.Handler; + +import java.util.ArrayDeque; + +/** + * Sample handler for the sake of the examples. + * + * A custom handler can be implemented to handle incoming samples, queries or replies for + * subscribers, get operations, query operations or queryables. + * + * The example below shows a queue handler, in which an ArrayDeque is specified as the receiver type. + * The function handle will be called everytime an element of type T is received and in our example + * implementation, elements are simply enqueued into the queue, which can later be retrieved. + */ +class QueueHandler implements Handler> { + + final ArrayDeque queue = new ArrayDeque<>(); + + @Override + public void handle(T t) { + queue.add(t); + } + + @Override + public ArrayDeque receiver() { + return queue; + } + + @Override + public void onClose() {} +} diff --git a/examples/src/main/java/io/zenoh/ZDelete.java b/examples/src/main/java/io/zenoh/ZDelete.java index 7f7e726c..03afce62 100644 --- a/examples/src/main/java/io/zenoh/ZDelete.java +++ b/examples/src/main/java/io/zenoh/ZDelete.java @@ -14,17 +14,89 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; +import picocli.CommandLine; -public class ZDelete { - public static void main(String[] args) throws ZenohException { +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZDelete", + mixinStandardHelpOptions = true, + description = "Zenoh Delete example" +) +public class ZDelete implements Callable { + + @Override + public Integer call() throws ZError { + Zenoh.initLogFromEnvOr("error"); System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-put")) { - System.out.println("Deleting resources matching '" + keyExpr + "'..."); - session.delete(keyExpr).res(); - } + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom(key); + System.out.println("Deleting resources matching '" + keyExpr + "'..."); + session.delete(keyExpr); } + return 0; + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZDelete(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to delete [default: demo/example/zenoh-java-delete].", + defaultValue = "demo/example/zenoh-java-delete" + ) + private String key; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZDelete(args.length == 0)).execute(args); + System.exit(exitCode); } } diff --git a/examples/src/main/java/io/zenoh/ZGet.java b/examples/src/main/java/io/zenoh/ZGet.java index a8dbb262..c840be96 100644 --- a/examples/src/main/java/io/zenoh/ZGet.java +++ b/examples/src/main/java/io/zenoh/ZGet.java @@ -14,37 +14,191 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.query.GetOptions; +import io.zenoh.query.QueryTarget; +import io.zenoh.query.Selector; import io.zenoh.query.Reply; -import io.zenoh.selector.Selector; +import io.zenoh.sample.SampleKind; +import picocli.CommandLine; +import java.time.Duration; +import java.util.List; import java.util.Optional; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; -public class ZGet { +import static io.zenoh.ConfigKt.loadConfig; - public static void main(String[] args) throws ZenohException, InterruptedException { +@CommandLine.Command( + name = "ZGet", + mixinStandardHelpOptions = true, + description = "Zenoh Get example" +) +public class ZGet implements Callable { + + @Override + public Integer call() throws ZError, InterruptedException { + Zenoh.initLogFromEnvOr("error"); System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (Selector selector = Selector.tryFrom("demo/example/**")) { - System.out.println("Performing Get on '" + selector + "'..."); - BlockingQueue> receiver = session.get(selector).res(); - assert receiver != null; - while (true) { - Optional wrapper = receiver.take(); - if (wrapper.isEmpty()) { - break; - } - Reply reply = wrapper.get(); - if (reply instanceof Reply.Success) { - Reply.Success successReply = (Reply.Success) reply; - System.out.println("Received ('" + successReply.getSample().getKeyExpr() + "': '" + successReply.getSample().getValue() + "')"); - } else { - Reply.Error errorReply = (Reply.Error) reply; - System.out.println("Received (ERROR: '" + errorReply.getError() + "')"); - } + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + Selector selector = Selector.tryFrom(this.selectorOpt); + + // Load GET options + GetOptions options = new GetOptions(); + options.setPayload(ZBytes.from(this.payload)); + options.setTarget(QueryTarget.valueOf(this.target)); + options.setTimeout(Duration.ofMillis(this.timeout)); + options.setAttachment(ZBytes.from(this.attachment)); + + + // A GET query can be performed in different ways, by default (using a blocking queue), using a callback + // or providing a handler. Uncomment one of the function calls below to try out different implementations: + // Implementation with a blocking queue + getExampleDefault(config, selector, options); + // getExampleWithCallback(config, selector, options); + // getExampleWithHandler(config, selector, options); + + return 0; + } + + private void getExampleDefault(Config config, Selector selector, GetOptions options) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + System.out.println("Performing Get on '" + selector + "'..."); + + BlockingQueue> receiver = session.get(selector, options); + + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; } + Reply reply = wrapper.get(); + handleReply(reply); } } } + + /** + * Example using a simple callback for handling the replies. + * @see io.zenoh.handlers.Callback + */ + private void getExampleWithCallback(Config config, Selector selector, GetOptions options) throws ZError { + try (Session session = Zenoh.open(config)) { + System.out.println("Performing Get on '" + selector + "'..."); + session.get(selector, this::handleReply, options); + } + } + + /** + * Example using a custom implementation of a Handler. + * @see QueueHandler + * @see io.zenoh.handlers.Handler + */ + private void getExampleWithHandler(Config config, Selector selector, GetOptions options) throws ZError { + try (Session session = Zenoh.open(config)) { + System.out.println("Performing Get on '" + selector + "'..."); + QueueHandler queueHandler = new QueueHandler<>(); + session.get(selector, queueHandler, options); + } + } + + private void handleReply(Reply reply) { + if (reply instanceof Reply.Success) { + Reply.Success successReply = (Reply.Success) reply; + if (successReply.getSample().getKind() == SampleKind.PUT) { + System.out.println("Received ('" + successReply.getSample().getKeyExpr() + "': '" + successReply.getSample().getPayload() + "')"); + } else if (successReply.getSample().getKind() == SampleKind.DELETE) { + System.out.println("Received (DELETE '" + successReply.getSample().getKeyExpr() + "')"); + } + } else { + Reply.Error errorReply = (Reply.Error) reply; + System.out.println("Received (ERROR: '" + errorReply.getError() + "')"); + } + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZGet(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-s", "--selector"}, + description = "The selection of resources to query [default: demo/example/**].", + defaultValue = "demo/example/**" + ) + private String selectorOpt; + + @CommandLine.Option( + names = {"-p", "--payload"}, + description = "An optional payload to put in the query." + ) + private String payload; + + @CommandLine.Option( + names = {"-t", "--target"}, + description = "The target queryables of the query. Default: BEST_MATCHING. " + + "[possible values: BEST_MATCHING, ALL, ALL_COMPLETE]" + ) + private String target; + + @CommandLine.Option( + names = {"-o", "--timeout"}, + description = "The query timeout in milliseconds [default: 10000].", + defaultValue = "10000" + ) + private long timeout; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-a", "--attach"}, + description = "The attachment to add to the get. The key-value pairs are &-separated, and = serves as the separator between key and value." + ) + private String attachment; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZGet(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZGetLiveliness.java b/examples/src/main/java/io/zenoh/ZGetLiveliness.java new file mode 100644 index 00000000..423bf402 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZGetLiveliness.java @@ -0,0 +1,162 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.query.Reply; +import picocli.CommandLine; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZGetLiveliness", + mixinStandardHelpOptions = true, + description = "Zenoh Get Liveliness example" +) +public class ZGetLiveliness implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + KeyExpr keyExpr = KeyExpr.tryFrom(this.key); + + // Uncomment one of the lines below to try out different implementations: + getLivelinessWithBlockingQueue(config, keyExpr); + // getLivelinessWithCallback(config, keyExpr); + // getLivelinessWithHandler(config, keyExpr); + + return 0; + } + + /** + * Default implementation using a blocking queue to handle replies. + */ + private void getLivelinessWithBlockingQueue(Config config, KeyExpr keyExpr) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + BlockingQueue> replyQueue = session.liveliness().get(keyExpr, Duration.ofMillis(timeout)); + + while (true) { + Optional wrapper = replyQueue.take(); + if (wrapper.isEmpty()) { + break; + } + handleReply(wrapper.get()); + } + } + } + + /** + * Example using a callback to handle liveliness replies asynchronously. + * @see io.zenoh.handlers.Callback + */ + private void getLivelinessWithCallback(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + session.liveliness().get(keyExpr, this::handleReply, Duration.ofMillis(timeout)); + } + } + + /** + * Example using a custom handler to process liveliness replies. + * @see QueueHandler + * @see io.zenoh.handlers.Handler + */ + private void getLivelinessWithHandler(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + QueueHandler queueHandler = new QueueHandler<>(); + session.liveliness().get(keyExpr, queueHandler, Duration.ofMillis(timeout)); + } + } + + private void handleReply(Reply reply) { + if (reply instanceof Reply.Success) { + Reply.Success successReply = (Reply.Success) reply; + System.out.println(">> Alive token ('" + successReply.getSample().getKeyExpr() + "')"); + } else if (reply instanceof Reply.Error) { + Reply.Error errorReply = (Reply.Error) reply; + System.out.println(">> Received (ERROR: '" + errorReply.getError() + "')"); + } + } + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZGetLiveliness(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression matching liveliness tokens to query. [default: group1/**].", + defaultValue = "group1/**" + ) + private String key; + + @CommandLine.Option( + names = {"-o", "--timeout"}, + description = "The query timeout in milliseconds [default: 10000].", + defaultValue = "10000" + ) + private long timeout; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZGetLiveliness(args.length == 0)).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZInfo.java b/examples/src/main/java/io/zenoh/ZInfo.java new file mode 100644 index 00000000..4d4bdb53 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZInfo.java @@ -0,0 +1,101 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.session.SessionInfo; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZInfo", + mixinStandardHelpOptions = true, + description = "Zenoh Info example" +) +public class ZInfo implements Callable { + + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening session..."); + try (Session session = Zenoh.open(config)) { + SessionInfo info = session.info(); + System.out.println("zid: " + info.zid()); + System.out.println("routers zid: " + info.routersZid()); + System.out.println("peers zid: " + info.peersZid()); + } catch (ZError e) { + System.err.println("Error during Zenoh operation: " + e.getMessage()); + return 1; + } + return 0; + } + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZInfo(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZInfo(args.length == 0)).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZLiveliness.java b/examples/src/main/java/io/zenoh/ZLiveliness.java new file mode 100644 index 00000000..b1df3c29 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZLiveliness.java @@ -0,0 +1,113 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZLiveliness", + mixinStandardHelpOptions = true, + description = "Zenoh Liveliness example" +) +public class ZLiveliness implements Callable { + + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening session..."); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom(key); + session.liveliness().declareToken(keyExpr); + System.out.println("Liveliness token declared for key: " + key); + + while (true) { + Thread.sleep(1000); + } + + } catch (ZError e) { + System.err.println("Error during Zenoh operation: " + e.getMessage()); + return 1; + } + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZLiveliness(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to declare liveliness tokens for [default: group1/zenoh-java].", + defaultValue = "group1/zenoh-java" + ) + private String key; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZLiveliness(args.length == 0)).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZPing.java b/examples/src/main/java/io/zenoh/ZPing.java new file mode 100644 index 00000000..76b3017c --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZPing.java @@ -0,0 +1,167 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Publisher; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.qos.CongestionControl; +import io.zenoh.sample.Sample; +import picocli.CommandLine; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPing", + mixinStandardHelpOptions = true, + description = "Zenoh Ping example" +) +public class ZPing implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + // Load Zenoh configuration + Config config = loadConfig(true, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening session..."); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExprPing = KeyExpr.tryFrom("test/ping"); + KeyExpr keyExprPong = KeyExpr.tryFrom("test/pong"); + + BlockingQueue> receiverQueue = + session.declareSubscriber(keyExprPong).getReceiver(); + + var publisherOptions = new PublisherOptions(); + publisherOptions.setCongestionControl(CongestionControl.BLOCK); + publisherOptions.setExpress(!noExpress); + Publisher publisher = session.declarePublisher(keyExprPing, publisherOptions); + + byte[] data = new byte[payloadSize]; + for (int i = 0; i < payloadSize; i++) { + data[i] = (byte) (i % 10); + } + ZBytes payload = ZBytes.from(data); + + // Warm-up + System.out.println("Warming up for " + warmup + " seconds..."); + long warmupEnd = System.currentTimeMillis() + (long) (warmup * 1000); + while (System.currentTimeMillis() < warmupEnd) { + publisher.put(payload); + receiverQueue.take(); + } + + List samples = new ArrayList<>(); + for (int i = 0; i < n; i++) { + long startTime = System.nanoTime(); + publisher.put(payload); + receiverQueue.take(); + long elapsedTime = (System.nanoTime() - startTime) / 1000; // Convert to microseconds + samples.add(elapsedTime); + } + + for (int i = 0; i < samples.size(); i++) { + long rtt = samples.get(i); + System.out.printf("%d bytes: seq=%d rtt=%dµs lat=%dµs%n", payloadSize, i, rtt, rtt / 2); + } + } catch (ZError e) { + System.err.println("Error: " + e.getMessage()); + return 1; + } + + return 0; + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + @CommandLine.Parameters( + paramLabel = "payload_size", + description = "Sets the size of the payload to publish [default: 8].", + defaultValue = "8" + ) + private int payloadSize; + + @CommandLine.Option( + names = "--no-express", + description = "Express for sending data.", + defaultValue = "false" + ) + private boolean noExpress; + + @CommandLine.Option( + names = {"-w", "--warmup"}, + description = "The number of seconds to warm up [default: 1.0].", + defaultValue = "1.0" + ) + private double warmup; + + @CommandLine.Option( + names = {"-n", "--samples"}, + description = "The number of round-trips to measure [default: 100].", + defaultValue = "100" + ) + private int n; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZPing()).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZPong.java b/examples/src/main/java/io/zenoh/ZPong.java new file mode 100644 index 00000000..580bd998 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZPong.java @@ -0,0 +1,127 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Publisher; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.qos.CongestionControl; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPong", + mixinStandardHelpOptions = true, + description = "Zenoh ZPong example" +) +public class ZPong implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(true, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening session..."); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExprPing = KeyExpr.tryFrom("test/ping"); + KeyExpr keyExprPong = KeyExpr.tryFrom("test/pong"); + + var publisherOptions = new PublisherOptions(); + publisherOptions.setCongestionControl(CongestionControl.BLOCK); + publisherOptions.setExpress(!noExpress); + + Publisher publisher = session.declarePublisher(keyExprPong, publisherOptions); + + session.declareSubscriber(keyExprPing, sample -> { + try { + publisher.put(sample.getPayload()); + } catch (ZError e) { + System.err.println("Error responding to ping: " + e.getMessage()); + } + }); + + latch.await(); + } catch (ZError e) { + System.err.println("Error: " + e.getMessage()); + return 1; + } + return 0; + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + @CommandLine.Option( + names = "--no-express", + description = "Express for sending data.", + defaultValue = "false" + ) + private boolean noExpress; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + private static final CountDownLatch latch = new CountDownLatch(1); + + public static void main(String[] args) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("Shutting down..."); + latch.countDown(); + })); + + int exitCode = new CommandLine(new ZPong()).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZPub.java b/examples/src/main/java/io/zenoh/ZPub.java index 24965fc8..319772a9 100644 --- a/examples/src/main/java/io/zenoh/ZPub.java +++ b/examples/src/main/java/io/zenoh/ZPub.java @@ -14,28 +14,137 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.publication.Publisher; +import io.zenoh.pubsub.Publisher; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.qos.CongestionControl; +import io.zenoh.qos.Reliability; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPub", + mixinStandardHelpOptions = true, + description = "Zenoh Pub example" +) +public class ZPub implements Callable { + + @Override + public Integer call() throws ZError { + Zenoh.initLogFromEnvOr("error"); + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); -public class ZPub { - public static void main(String[] args) throws ZenohException, InterruptedException { System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-pub")) { - System.out.println("Declaring publisher on '" + keyExpr + "'..."); - try (Publisher publisher = session.declarePublisher(keyExpr).res()) { - System.out.println("Press CTRL-C to quit..."); - int idx = 0; - while (true) { - Thread.sleep(1000); - String payload = String.format("[%4s] Pub from Java!", idx); - System.out.println("Putting Data ('" + keyExpr + "': '"+payload+"')..."); - publisher.put(payload).res(); - idx++; - } + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom(key); + System.out.println("Declaring publisher on '" + keyExpr + "'..."); + + // A publisher config can optionally be provided. + PublisherOptions publisherOptions = new PublisherOptions(); + publisherOptions.setEncoding(Encoding.ZENOH_STRING); + publisherOptions.setCongestionControl(CongestionControl.BLOCK); + publisherOptions.setReliability(Reliability.RELIABLE); + + // Declare the publisher + Publisher publisher = session.declarePublisher(keyExpr, publisherOptions); + + System.out.println("Press CTRL-C to quit..."); + ZBytes attachmentBytes = attachment != null ? ZBytes.from(attachment) : null; + int idx = 0; + while (true) { + Thread.sleep(1000); + String payload = String.format("[%4d] %s", idx, value); + System.out.println("Putting Data ('" + keyExpr + "': '" + payload + "')..."); + if (attachmentBytes != null) { + PutOptions putOptions = new PutOptions(); + putOptions.setAttachment(attachmentBytes); + publisher.put(ZBytes.from(payload), putOptions); + } else { + publisher.put(ZBytes.from(payload)); } + idx++; } + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + return 1; } } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZPub(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to write to [default: demo/example/zenoh-java-pub].", + defaultValue = "demo/example/zenoh-java-pub" + ) + private String key; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"-v", "--value"}, + description = "The value to write. [default: 'Pub from Java!']", + defaultValue = "Pub from Java!" + ) + private String value; + + @CommandLine.Option( + names = {"-a", "--attach"}, + description = "The attachments to add to each put. The key-value pairs are &-separated, and = serves as the separator between key and value." + ) + private String attachment; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZPub(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZPubThr.java b/examples/src/main/java/io/zenoh/ZPubThr.java index 1d1b42d9..8482f2b5 100644 --- a/examples/src/main/java/io/zenoh/ZPubThr.java +++ b/examples/src/main/java/io/zenoh/ZPubThr.java @@ -14,32 +14,142 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.ZBytes; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.prelude.CongestionControl; -import io.zenoh.prelude.Encoding; -import io.zenoh.publication.Publisher; -import io.zenoh.value.Value; +import io.zenoh.pubsub.Publisher; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.qos.CongestionControl; +import io.zenoh.qos.Priority; +import picocli.CommandLine; -public class ZPubThr { +import java.util.List; +import java.util.concurrent.Callable; - public static void main(String[] args) throws ZenohException { - int size = 8; - byte[] data = new byte[size]; - for (int i = 0; i < size; i++) { +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPubThr", + mixinStandardHelpOptions = true, + description = "Zenoh Throughput example" +) +public class ZPubThr implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + byte[] data = new byte[payloadSize]; + for (int i = 0; i < payloadSize; i++) { data[i] = (byte) (i % 10); } - Value value = new Value(data, new Encoding(Encoding.ID.ZENOH_BYTES, null)); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("test/thr")) { - try (Publisher publisher = session.declarePublisher(keyExpr).congestionControl(CongestionControl.BLOCK).res()) { - System.out.println("Publisher declared on test/thr."); - System.out.println("Press CTRL-C to quit..."); - while (true) { - publisher.put(value).res(); + ZBytes payload = ZBytes.from(data); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom("test/thr"); + var publisherOptions = new PublisherOptions(); + publisherOptions.setCongestionControl(CongestionControl.BLOCK); + publisherOptions.setPriority(priorityInput != null ? Priority.getEntries().get(priorityInput) : Priority.DATA); + try (Publisher publisher = session.declarePublisher(keyExpr, publisherOptions)) { + System.out.println("Publisher declared on test/thr."); + long count = 0; + long start = System.currentTimeMillis(); + System.out.println("Press CTRL-C to quit..."); + + while (true) { + publisher.put(payload); + + if (statsPrint) { + if (count < number) { + count++; + } else { + long elapsedTime = System.currentTimeMillis() - start; + long throughput = (count * 1000) / elapsedTime; + System.out.println(throughput + " msgs/s"); + count = 0; + start = System.currentTimeMillis(); + } } } } } } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZPubThr(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Parameters( + index = "0", + description = "Sets the size of the payload to publish [default: 8].", + defaultValue = "8" + ) + private int payloadSize; + + @CommandLine.Option( + names = {"-p", "--priority"}, + description = "Priority for sending data." + ) + private Integer priorityInput; + + @CommandLine.Option( + names = {"-n", "--number"}, + description = "Number of messages in each throughput measurement [default: 100000].", + defaultValue = "100000" + ) + private long number; + + @CommandLine.Option( + names = {"-t", "--print"}, + description = "Print the statistics.", + defaultValue = "true" + ) + private boolean statsPrint; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZPubThr(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZPut.java b/examples/src/main/java/io/zenoh/ZPut.java index 60317b81..11172062 100644 --- a/examples/src/main/java/io/zenoh/ZPut.java +++ b/examples/src/main/java/io/zenoh/ZPut.java @@ -14,24 +14,116 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.prelude.SampleKind; -import io.zenoh.prelude.CongestionControl; -import io.zenoh.prelude.Priority; +import io.zenoh.pubsub.PutOptions; +import picocli.CommandLine; + +import java.util.List; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZPut", + mixinStandardHelpOptions = true, + description = "Zenoh Put example" +) +public class ZPut implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); -public class ZPut { - public static void main(String[] args) throws ZenohException { System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-put")) { - String value = "Put from Java!"; - session.put(keyExpr, value) - .congestionControl(CongestionControl.BLOCK) - .priority(Priority.REALTIME) - .res(); - System.out.println("Putting Data ('" + keyExpr + "': '" + value + "')..."); + try (Session session = Zenoh.open(config)) { + KeyExpr keyExpr = KeyExpr.tryFrom(key); + System.out.println("Putting Data ('" + keyExpr + "': '" + value + "')..."); + if (attachment != null) { + var putOptions = new PutOptions(); + putOptions.setAttachment(ZBytes.from(attachment)); + session.put(keyExpr, ZBytes.from(value), putOptions); + } else { + session.put(keyExpr, ZBytes.from(value)); } + } catch (ZError e) { + System.err.println("Error during Zenoh operation: " + e.getMessage()); + return 1; } + + return 0; + } + + + /** + * ----- Example CLI arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZPut(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to write to [default: demo/example/zenoh-java-put].", + defaultValue = "demo/example/zenoh-java-put" + ) + private String key; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"-v", "--value"}, + description = "The value to write. [default: 'Put from Java!'].", + defaultValue = "Put from Java!" + ) + private String value; + + @CommandLine.Option( + names = {"-a", "--attach"}, + description = "The attachment to add to the put. The key-value pairs are &-separated, and = serves as the separator between key and value." + ) + private String attachment; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZPut(args.length == 0)).execute(args); + System.exit(exitCode); } } diff --git a/examples/src/main/java/io/zenoh/ZQueryable.java b/examples/src/main/java/io/zenoh/ZQueryable.java index c5a9c872..6cb68e00 100644 --- a/examples/src/main/java/io/zenoh/ZQueryable.java +++ b/examples/src/main/java/io/zenoh/ZQueryable.java @@ -14,47 +14,158 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.prelude.SampleKind; -import io.zenoh.queryable.Query; -import io.zenoh.queryable.Queryable; +import io.zenoh.query.Query; +import io.zenoh.query.QueryableOptions; +import io.zenoh.query.ReplyOptions; import org.apache.commons.net.ntp.TimeStamp; +import picocli.CommandLine; +import java.util.List; import java.util.Optional; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; -public class ZQueryable { - - public static void main(String[] args) throws ZenohException, InterruptedException { - String keyExprString = "demo/example/zenoh-java-queryable"; - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom(keyExprString)) { - System.out.println("Declaring Queryable on " + keyExprString + "..."); - try (Queryable>> queryable = session.declareQueryable(keyExpr).res()) { - BlockingQueue> receiver = queryable.getReceiver(); - assert receiver != null; - System.out.println("Press CTRL-C to quit..."); - handleRequests(receiver, keyExpr); +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZQueryable", + mixinStandardHelpOptions = true, + description = "Zenoh Queryable example" +) +public class ZQueryable implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + KeyExpr keyExpr = KeyExpr.tryFrom(this.key); + + // A Queryable can be implemented in multiple ways. Uncomment one to try: + declareQueryableWithBlockingQueue(config, keyExpr); + // declareQueryableWithCallback(config, keyExpr); + // declareQueryableProvidingConfig(config, keyExpr); + + return 0; + } + + /** + * Default implementation using a blocking queue to handle incoming queries. + */ + private void declareQueryableWithBlockingQueue(Config config, KeyExpr keyExpr) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + var queryable = session.declareQueryable(keyExpr); + BlockingQueue> receiver = queryable.getReceiver(); + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; } + Query query = wrapper.get(); + handleQuery(query); } } } - private static void handleRequests(BlockingQueue> receiver, KeyExpr keyExpr) throws InterruptedException { - while (true) { - Optional wrapper = receiver.take(); - if (wrapper.isEmpty()) { - break; - } - Query query = wrapper.get(); - String valueInfo = query.getValue() != null ? " with value '" + query.getValue() + "'" : ""; + /** + * Example using a callback to handle incoming queries asynchronously. + * + * @see io.zenoh.handlers.Callback + */ + private void declareQueryableWithCallback(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + session.declareQueryable(keyExpr, this::handleQuery); + } + } + + /** + * Example demonstrating the use of QueryableConfig to declare a Queryable. + * + * @see QueryableOptions + */ + private void declareQueryableProvidingConfig(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + QueryableOptions queryableOptions = new QueryableOptions(); + queryableOptions.setComplete(true); + session.declareQueryable(keyExpr, this::handleQuery, queryableOptions); + } + } + + private void handleQuery(Query query) { + try { + String valueInfo = query.getPayload() != null ? " with value '" + query.getPayload() + "'" : ""; System.out.println(">> [Queryable] Received Query '" + query.getSelector() + "'" + valueInfo); - try { - query.reply(keyExpr).success("Queryable from Java!").timestamp(TimeStamp.getCurrentTime()).res(); - } catch (Exception e) { - System.out.println(">> [Queryable] Error sending reply: " + e); - } + var options = new ReplyOptions(); + options.setTimeStamp(TimeStamp.getCurrentTime()); + query.reply(query.getKeyExpr(), ZBytes.from(value), options); + } catch (Exception e) { + System.err.println(">> [Queryable] Error sending reply: " + e.getMessage()); } } + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZQueryable(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to write to [default: demo/example/zenoh-java-queryable].", + defaultValue = "demo/example/zenoh-java-queryable" + ) + private String key; + + @CommandLine.Option( + names = {"-v", "--value"}, + description = "The value to reply to queries [default: 'Queryable from Java!'].", + defaultValue = "Queryable from Java!" + ) + private String value; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZQueryable(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZScout.java b/examples/src/main/java/io/zenoh/ZScout.java new file mode 100644 index 00000000..89f2be1b --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZScout.java @@ -0,0 +1,67 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.config.WhatAmI; +import io.zenoh.scouting.Hello; +import io.zenoh.scouting.ScoutOptions; +import picocli.CommandLine; + +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; + +@CommandLine.Command( + name = "ZScout", + mixinStandardHelpOptions = true, + description = "Zenoh Scouting example" +) +public class ZScout implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + System.out.println("Scouting..."); + + var scoutOptions = new ScoutOptions(); + scoutOptions.setWhatAmI(Set.of(WhatAmI.Peer, WhatAmI.Router)); + var scout = Zenoh.scout(scoutOptions); + BlockingQueue> receiver = scout.getReceiver(); + assert receiver != null; + + try { + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; + } + + Hello hello = wrapper.get(); + System.out.println(hello); + } + } finally { + scout.stop(); + } + + return 0; + } + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZScout()).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZSub.java b/examples/src/main/java/io/zenoh/ZSub.java index 883139f7..2379e70f 100644 --- a/examples/src/main/java/io/zenoh/ZSub.java +++ b/examples/src/main/java/io/zenoh/ZSub.java @@ -14,35 +14,149 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.exceptions.ZError; +import io.zenoh.handlers.Handler; import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.HandlerSubscriber; import io.zenoh.sample.Sample; -import io.zenoh.subscriber.Subscriber; +import picocli.CommandLine; +import java.util.List; import java.util.Optional; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; -public class ZSub { - - public static void main(String[] args) throws ZenohException, InterruptedException { - System.out.println("Opening session..."); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/**")) { - System.out.println("Declaring Subscriber on '" + keyExpr + "'..."); - try (Subscriber>> subscriber = session.declareSubscriber(keyExpr).res()) { - BlockingQueue> receiver = subscriber.getReceiver(); - assert receiver != null; - System.out.println("Press CTRL-C to quit..."); - while (true) { - Optional wrapper = receiver.take(); - if (wrapper.isEmpty()) { - break; - } - Sample sample = wrapper.get(); - System.out.println(">> [Subscriber] Received " + sample.getKind() + " ('" + sample.getKeyExpr() + "': '" + sample.getValue() + "')"); +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZSub", + mixinStandardHelpOptions = true, + description = "Zenoh Sub example" +) +public class ZSub implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + KeyExpr keyExpr = KeyExpr.tryFrom(this.key); + + // Subscribers can be declared in different ways. + // Uncomment one of the lines below to try out different implementations: + subscribeWithBlockingQueue(config, keyExpr); + // subscribeWithCallback(config, keyExpr); + // subscribeWithHandler(config, keyExpr); + + return 0; + } + + /** + * Default implementation using a blocking queue to handle incoming samples. + */ + private void subscribeWithBlockingQueue(Config config, KeyExpr keyExpr) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + try (HandlerSubscriber>> subscriber = session.declareSubscriber(keyExpr)) { + BlockingQueue> receiver = subscriber.getReceiver(); + assert receiver != null; + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; } + handleSample(wrapper.get()); } } } } + + /** + * Example using a callback to handle incoming samples asynchronously. + * @see io.zenoh.handlers.Callback + */ + private void subscribeWithCallback(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + session.declareSubscriber(keyExpr, this::handleSample); + } + } + + /** + * Example using a custom implementation of the Handler. + * @see QueueHandler + * @see Handler + */ + private void subscribeWithHandler(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + QueueHandler queueHandler = new QueueHandler<>(); + var subscriber = session.declareSubscriber(keyExpr, queueHandler); + for (Sample sample : subscriber.getReceiver()) { + System.out.println(sample); + } + } + } + + /** + * Handles a single Sample and prints relevant information. + */ + private void handleSample(Sample sample) { + String attachment = sample.getAttachment() != null ? ", with attachment: " + sample.getAttachment() : ""; + System.out.println(">> [Subscriber] Received " + sample.getKind() + + " ('" + sample.getKeyExpr() + "': '" + sample.getPayload() + "'" + attachment + ")"); + } + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZSub(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to subscribe to [default: demo/example/**].", + defaultValue = "demo/example/**" + ) + private String key; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZSub(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/examples/src/main/java/io/zenoh/ZSubLiveliness.java b/examples/src/main/java/io/zenoh/ZSubLiveliness.java new file mode 100644 index 00000000..cf76c9e2 --- /dev/null +++ b/examples/src/main/java/io/zenoh/ZSubLiveliness.java @@ -0,0 +1,181 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.liveliness.LivelinessSubscriberOptions; +import io.zenoh.sample.Sample; +import io.zenoh.sample.SampleKind; +import picocli.CommandLine; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; + +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZSubLiveliness", + mixinStandardHelpOptions = true, + description = "Zenoh Sub Liveliness example" +) +public class ZSubLiveliness implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + KeyExpr keyExpr = KeyExpr.tryFrom(this.key); + + // Subscribing to liveliness tokens can be implemented in multiple ways. + // Uncomment the desired implementation: + subscribeToLivelinessWithBlockingQueue(config, keyExpr); + // subscribeToLivelinessWithCallback(config, keyExpr); + // subscribeToLivelinessWithHandler(config, keyExpr); + + return 0; + } + + /** + * Default implementation using a blocking queue to handle incoming liveliness tokens. + */ + private void subscribeToLivelinessWithBlockingQueue(Config config, KeyExpr keyExpr) throws ZError, InterruptedException { + try (Session session = Zenoh.open(config)) { + var options = new LivelinessSubscriberOptions(history); + var subscriber = session.liveliness().declareSubscriber(keyExpr, options); + + BlockingQueue> receiver = subscriber.getReceiver(); + System.out.println("Listening for liveliness tokens..."); + while (true) { + Optional wrapper = receiver.take(); + if (wrapper.isEmpty()) { + break; + } + handleLivelinessSample(wrapper.get()); + } + } + } + + /** + * Example using a callback to handle incoming liveliness tokens asynchronously. + * + * @see io.zenoh.handlers.Callback + */ + private void subscribeToLivelinessWithCallback(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + var options = new LivelinessSubscriberOptions(history); + session.liveliness().declareSubscriber( + keyExpr, + this::handleLivelinessSample, + options + ); + } + } + + /** + * Example using a handler to handle incoming liveliness tokens asynchronously. + * + * @see io.zenoh.handlers.Handler + * @see QueueHandler + */ + private void subscribeToLivelinessWithHandler(Config config, KeyExpr keyExpr) throws ZError { + try (Session session = Zenoh.open(config)) { + QueueHandler queueHandler = new QueueHandler<>(); + var options = new LivelinessSubscriberOptions(history); + session.liveliness().declareSubscriber( + keyExpr, + queueHandler, + options + ); + } + } + + /** + * Handles a single liveliness token sample. + */ + private void handleLivelinessSample(Sample sample) { + if (sample.getKind() == SampleKind.PUT) { + System.out.println(">> [LivelinessSubscriber] New alive token ('" + sample.getKeyExpr() + "')"); + } else if (sample.getKind() == SampleKind.DELETE) { + System.out.println(">> [LivelinessSubscriber] Dropped token ('" + sample.getKeyExpr() + "')"); + } + } + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZSubLiveliness(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-k", "--key"}, + description = "The key expression to subscribe to [default: group1/**].", + defaultValue = "group1/**" + ) + private String key; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--history"}, + description = "Get historical liveliness tokens.", + defaultValue = "false" + ) + private boolean history; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZSubLiveliness(args.length == 0)).execute(args); + System.exit(exitCode); + } +} diff --git a/examples/src/main/java/io/zenoh/ZSubThr.java b/examples/src/main/java/io/zenoh/ZSubThr.java index d7afc00a..670967ee 100644 --- a/examples/src/main/java/io/zenoh/ZSubThr.java +++ b/examples/src/main/java/io/zenoh/ZSubThr.java @@ -14,61 +14,164 @@ package io.zenoh; -import io.zenoh.exceptions.ZenohException; +import io.zenoh.exceptions.ZError; import io.zenoh.keyexpr.KeyExpr; -import io.zenoh.subscriber.Subscriber; -import kotlin.Unit; +import io.zenoh.pubsub.Subscriber; +import picocli.CommandLine; -public class ZSubThr { +import java.util.List; +import java.util.concurrent.Callable; - private static final long NANOS_TO_SEC = 1_000_000_000L; - private static final long n = 50000L; - private static int batchCount = 0; - private static int count = 0; - private static long startTimestampNs = 0; - private static long globalStartTimestampNs = 0; +import static io.zenoh.ConfigKt.loadConfig; + +@CommandLine.Command( + name = "ZSubThr", + mixinStandardHelpOptions = true, + description = "Zenoh Subscriber Throughput test" +) +public class ZSubThr implements Callable { + + @Override + public Integer call() throws Exception { + Zenoh.initLogFromEnvOr("error"); + + Config config = loadConfig(emptyArgs, configFile, connect, listen, noMulticastScouting, mode); + + System.out.println("Opening Session"); + try (Session session = Zenoh.open(config)) { + try (KeyExpr keyExpr = KeyExpr.tryFrom("test/thr")) { + subscriber = session.declareSubscriber(keyExpr, sample -> listener(number)); + System.out.println("Press CTRL-C to quit..."); + + while (subscriber.isValid()) { + Thread.sleep(1000); + } + } + } catch (ZError e) { + System.err.println("Error during Zenoh operation: " + e.getMessage()); + return 1; + } + return 0; + } + + private void listener(long number) { + if (batchCount > samples) { + closeSubscriber(); + report(); + return; + } - public static void listener() { if (count == 0) { startTimestampNs = System.nanoTime(); - if (globalStartTimestampNs == 0L) { + if (globalStartTimestampNs == 0) { globalStartTimestampNs = startTimestampNs; } count++; return; } - if (count < n) { + + if (count < number) { count++; return; } + long stop = System.nanoTime(); - double msgs = (double) (n * NANOS_TO_SEC) / (stop - startTimestampNs); - System.out.println(msgs + " msgs/sec"); + double elapsedTimeSecs = (double) (stop - startTimestampNs) / NANOS_TO_SEC; + double messagesPerSec = number / elapsedTimeSecs; + System.out.printf("%.2f msgs/sec%n", messagesPerSec); batchCount++; count = 0; } - // TODO: perform report at end of measurement - public static void report() { + private void report() { long end = System.nanoTime(); - long total = batchCount * n + count; - double msgs = (double) (end - globalStartTimestampNs) / NANOS_TO_SEC; - double avg = (double) (total * NANOS_TO_SEC) / (end - globalStartTimestampNs); - System.out.println("Received " + total + " messages in " + msgs + - ": averaged " + avg + " msgs/sec"); + long totalMessages = batchCount * number + count; + double elapsedTimeSecs = (double) (end - globalStartTimestampNs) / NANOS_TO_SEC; + double averageMessagesPerSec = totalMessages / elapsedTimeSecs; + + System.out.printf("Received %d messages in %.2f seconds: averaged %.2f msgs/sec%n", + totalMessages, elapsedTimeSecs, averageMessagesPerSec); } - public static void main(String[] args) throws ZenohException, InterruptedException { - System.out.println("Opening Session"); - try (Session session = Session.open()) { - try (KeyExpr keyExpr = KeyExpr.tryFrom("test/thr")) { - try (Subscriber subscriber = session.declareSubscriber(keyExpr).with(sample -> listener()).res()) { - System.out.println("Press CTRL-C to quit..."); - while (true) { - Thread.sleep(1000); - } - } + private void closeSubscriber() { + if (subscriber != null && subscriber.isValid()) { + try { + subscriber.close(); + } catch (Exception e) { + System.err.println("Error closing subscriber: " + e.getMessage()); } } } + + + /** + * ----- Example arguments and private fields ----- + */ + + private final Boolean emptyArgs; + + ZSubThr(Boolean emptyArgs) { + this.emptyArgs = emptyArgs; + } + + private static final long NANOS_TO_SEC = 1_000_000_000L; + private long batchCount = 0; + private long count = 0; + private long startTimestampNs = 0; + private long globalStartTimestampNs = 0; + + @CommandLine.Option( + names = {"-s", "--samples"}, + description = "Number of throughput measurements [default: 10].", + defaultValue = "10" + ) + private long samples; + + @CommandLine.Option( + names = {"-n", "--number"}, + description = "Number of messages in each throughput measurement [default: 100000].", + defaultValue = "100000" + ) + private long number; + + @CommandLine.Option( + names = {"-c", "--config"}, + description = "A configuration file." + ) + private String configFile; + + @CommandLine.Option( + names = {"-e", "--connect"}, + description = "Endpoints to connect to.", + split = "," + ) + private List connect; + + @CommandLine.Option( + names = {"-l", "--listen"}, + description = "Endpoints to listen on.", + split = "," + ) + private List listen; + + @CommandLine.Option( + names = {"-m", "--mode"}, + description = "The session mode. Default: peer. Possible values: [peer, client, router].", + defaultValue = "peer" + ) + private String mode; + + @CommandLine.Option( + names = {"--no-multicast-scouting"}, + description = "Disable the multicast-based scouting mechanism.", + defaultValue = "false" + ) + private boolean noMulticastScouting; + + private Subscriber subscriber; + + public static void main(String[] args) { + int exitCode = new CommandLine(new ZSubThr(args.length == 0)).execute(args); + System.exit(exitCode); + } } diff --git a/zenoh-java/build.gradle.kts b/zenoh-java/build.gradle.kts index f5841d0a..d97ff9e2 100644 --- a/zenoh-java/build.gradle.kts +++ b/zenoh-java/build.gradle.kts @@ -46,6 +46,7 @@ kotlin { val zenohPaths = "../zenoh-jni/target/$buildMode" jvmArgs("-Djava.library.path=$zenohPaths") } + withJava() } if (androidEnabled) { androidTarget { @@ -58,7 +59,6 @@ kotlin { val commonMain by getting { dependencies { implementation("commons-net:commons-net:3.9.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") } } val commonTest by getting { diff --git a/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt b/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt index c8d9ad08..74d42e51 100644 --- a/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt +++ b/zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt @@ -18,24 +18,10 @@ package io.zenoh * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the * log level configuration. */ -internal actual class Zenoh private actual constructor() { - - actual companion object { - private const val ZENOH_LIB_NAME = "zenoh_jni" - private const val ZENOH_LOGS_PROPERTY = "zenoh.logger" - - private var instance: Zenoh? = null - - actual fun load() { - instance ?: Zenoh().also { instance = it } - } - } +internal actual object ZenohLoad { + private const val ZENOH_LIB_NAME = "zenoh_jni" init { System.loadLibrary(ZENOH_LIB_NAME) - val logLevel = System.getProperty(ZENOH_LOGS_PROPERTY) - if (logLevel != null) { - Logger.start(logLevel) - } } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt index 2e2964a7..91bc0e01 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Config.kt @@ -14,56 +14,146 @@ package io.zenoh +import io.zenoh.exceptions.ZError +import io.zenoh.jni.JNIConfig import java.io.File import java.nio.file.Path -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement - /** - * Config class to set the Zenoh configuration to be used through a [Session]. + * # Config + * + * Config class to set the Zenoh configuration to be used through a [io.zenoh.Session]. + * + * The configuration can be specified in two different ways: + * - By providing a file or a path to a file with the configuration + * - By providing a raw string configuration. + * + * Either way, the supported formats are `yaml`, `json` and `json5`. * - * @property path The path to the configuration file. - * @constructor Create empty Config + * A default configuration can be loaded using [Config.loadDefault]. + * + * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more + * information on the Zenoh config parameters. */ -class Config private constructor(internal val path: Path? = null, internal val jsonConfig: JsonElement? = null) { +class Config internal constructor(internal val jniConfig: JNIConfig) { companion object { + private const val CONFIG_ENV = "ZENOH_CONFIG" + /** - * Loads the default zenoh configuration. + * Returns the default config. */ - fun default(): Config { - return Config() + @JvmStatic + fun loadDefault(): Config { + return JNIConfig.loadDefaultConfig() } /** * Loads the configuration from the [File] specified. * - * @param file The zenoh config file. + * @param file The Zenoh config file. Supported types are: JSON, JSON5 and YAML. + * Note the format is determined after the file extension. + * @return The [Config]. */ - fun from(file: File): Config { - return Config(file.toPath()) + @JvmStatic + @Throws(ZError::class) + fun fromFile(file: File): Config { + return JNIConfig.loadConfigFile(file) } /** * Loads the configuration from the [Path] specified. * - * @param path The zenoh config file path. + * @param path Path to the Zenoh config file. Supported types are: JSON, JSON5 and YAML. + * Note the format is determined after the file extension. + * @return The [Config]. */ - fun from(path: Path): Config { - return Config(path) + @JvmStatic + @Throws(ZError::class) + fun fromFile(path: Path): Config { + return JNIConfig.loadConfigFile(path) } /** - * Loads the configuration from the [json] specified. + * Loads the configuration from json-formatted string. * - * @param json The zenoh raw zenoh config. + * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more + * information on the Zenoh config parameters. + * + * @param config Json formatted config. + * @return The [Config]. */ - fun from(json: String): Config { - return Config(jsonConfig = Json.decodeFromString(json)) + @JvmStatic + @Throws(ZError::class) + fun fromJson(config: String): Config { + return JNIConfig.loadJsonConfig(config) } + + /** + * Loads the configuration from json5-formatted string. + * + * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more + * information on the Zenoh config parameters. + * + * @param config Json5 formatted config + * @return The [Config]. + */ + @JvmStatic + @Throws(ZError::class) + fun fromJson5(config: String): Config { + return JNIConfig.loadJson5Config(config) + } + + /** + * Loads the configuration from yaml-formatted string. + * + * Visit the [default configuration](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5) for more + * information on the Zenoh config parameters. + * + * @param config Yaml formatted config + * @return The [Config]. + */ + @JvmStatic + @Throws(ZError::class) + fun fromYaml(config: String): Config { + return JNIConfig.loadYamlConfig(config) + } + + /** + * Loads the configuration from the env variable [CONFIG_ENV]. + * + * @return The config. + */ + @JvmStatic + @Throws(ZError::class) + fun fromEnv(): Config { + val envValue = System.getenv(CONFIG_ENV) + if (envValue != null) { + return fromFile(File(envValue)) + } else { + throw Exception("Couldn't load env variable: $CONFIG_ENV.") + } + } + } + + /** + * The json value associated to the [key]. + */ + @Throws(ZError::class) + fun getJson(key: String): String { + return jniConfig.getJson(key) } - constructor(jsonConfig: JsonElement) : this(null, jsonConfig = jsonConfig) + /** + * Inserts a json5 value associated to the [key] into the Config. + */ + @Throws(ZError::class) + fun insertJson5(key: String, value: String) { + jniConfig.insertJson5(key, value) + } + + protected fun finalize() { + jniConfig.close() + } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt index db25cf2f..eba6681c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Logger.kt @@ -14,14 +14,27 @@ package io.zenoh +import io.zenoh.exceptions.ZError + /** Logger class to redirect the Rust logs from Zenoh to the kotlin environment. */ -class Logger { +internal class Logger { companion object { + + internal const val LOG_ENV: String = "RUST_LOG" + + @Throws(ZError::class) + fun start(filter: String) { + startLogsViaJNI(filter) + } + /** * Redirects the rust logs either to logcat for Android systems or to the standard output (for non-android - * systems). @param logLevel must be either "info", "debug", "warn", "trace" or "error". + * systems). + * + * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. */ - external fun start(logLevel: String) + @Throws(ZError::class) + private external fun startLogsViaJNI(filter: String) } -} \ No newline at end of file +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt index c5e50d67..c944e83c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Session.kt @@ -14,26 +14,25 @@ package io.zenoh -import io.zenoh.exceptions.SessionException -import io.zenoh.exceptions.ZenohException +import io.zenoh.bytes.IntoZBytes +import io.zenoh.config.ZenohId +import io.zenoh.exceptions.ZError +import io.zenoh.handlers.BlockingQueueHandler import io.zenoh.handlers.Callback +import io.zenoh.handlers.Handler import io.zenoh.jni.JNISession import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.QoS -import io.zenoh.publication.Delete -import io.zenoh.publication.Publisher -import io.zenoh.publication.Put +import io.zenoh.liveliness.Liveliness +import io.zenoh.pubsub.* import io.zenoh.query.* -import io.zenoh.queryable.Query -import io.zenoh.queryable.Queryable +import io.zenoh.query.Query +import io.zenoh.query.Queryable import io.zenoh.sample.Sample -import io.zenoh.selector.Selector -import io.zenoh.subscriber.Reliability -import io.zenoh.subscriber.Subscriber -import io.zenoh.value.Value -import java.time.Duration +import io.zenoh.session.SessionDeclaration +import io.zenoh.session.SessionInfo import java.util.* import java.util.concurrent.BlockingQueue +import java.util.concurrent.LinkedBlockingDeque /** * A Zenoh Session, the core interaction point with a Zenoh network. @@ -41,7 +40,6 @@ import java.util.concurrent.BlockingQueue * A session is typically associated with declarations such as [Publisher]s, [Subscriber]s, or [Queryable]s, which are * declared using [declarePublisher], [declareSubscriber], and [declareQueryable], respectively. * Other operations such as simple Put, Get or Delete can be performed from a session using [put], [get] and [delete]. - * Finally, it's possible to declare key expressions ([KeyExpr]) as well. * * Sessions are open upon creation and can be closed manually by calling [close]. Alternatively, the session will be * automatically closed when used with Java's try-with-resources statement or its Kotlin counterpart, [use]. @@ -52,44 +50,28 @@ import java.util.concurrent.BlockingQueue */ class Session private constructor(private val config: Config) : AutoCloseable { - private var jniSession: JNISession? = JNISession() + internal var jniSession: JNISession? = JNISession() + + private var declarations = mutableListOf() companion object { - private val sessionClosedException = SessionException("Session is closed.") - - /** - * Open a [Session] with the default [Config]. - * - * @return The opened [Session]. - * @throws [SessionException] in the case of a failure. - */ - @JvmStatic - @Throws(SessionException::class) - fun open(): Session { - val session = Session(Config.default()) - return session.launch() - } + internal val sessionClosedException = ZError("Session is closed.") /** * Open a [Session] with the provided [Config]. * * @param config The configuration for the session. * @return The opened [Session]. - * @throws [SessionException] in the case of a failure. + * @throws [ZError] in the case of a failure. */ - @JvmStatic - @Throws(SessionException::class) - fun open(config: Config): Session { + @Throws(ZError::class) + internal fun open(config: Config): Session { val session = Session(config) return session.launch() } } - init { - Zenoh.load() - } - /** * Close the session. * @@ -98,15 +80,18 @@ class Session private constructor(private val config: Config) : AutoCloseable { * * However, any session declaration that was still alive and bound to the session previous to closing it, will still be alive. */ - @Throws(SessionException::class) override fun close() { + declarations.removeIf { + it.undeclare() + true + } + jniSession?.close() jniSession = null } - @Suppress("removal") protected fun finalize() { - jniSession?.close() + close() } /** @@ -114,93 +99,227 @@ class Session private constructor(private val config: Config) : AutoCloseable { * * Example: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/greeting")) { - * try (Publisher publisher = session.declarePublisher(keyExpr) - * .priority(Priority.REALTIME) - * .congestionControl(CongestionControl.DROP) - * .res()) { - * int idx = 0; - * while (true) { - * String payload = "Hello for the " + idx + "th time!"; - * publisher.put(payload).res(); - * Thread.sleep(1000); - * idx++; - * } - * } + * try (Session session = Zenoh.open(config)) { + * // A publisher config can optionally be provided. + * PublisherOptions publisherOptions = new PublisherOptions(); + * publisherOptions.setEncoding(Encoding.ZENOH_STRING); + * publisherOptions.setCongestionControl(CongestionControl.BLOCK); + * publisherOptions.setReliability(Reliability.RELIABLE); + * + * // Declare the publisher + * Publisher publisher = session.declarePublisher(keyExpr, publisherOptions); + * + * int idx = 0; + * while (true) { + * Thread.sleep(1000); + * String payload = String.format("[%4d] %s", idx, value); + * System.out.println("Putting Data ('" + keyExpr + "': '" + payload + "')..."); + * publisher.put(ZBytes.from(payload)); + * idx++; * } * } * ``` * * @param keyExpr The [KeyExpr] the publisher will be associated to. - * @return A resolvable [Publisher.Builder] + * @param publisherOptions Optional [PublisherOptions] to configure the publisher. + * @return The declared [Publisher]. */ - fun declarePublisher(keyExpr: KeyExpr): Publisher.Builder = Publisher.Builder(this, keyExpr) + @JvmOverloads + @Throws(ZError::class) + fun declarePublisher(keyExpr: KeyExpr, publisherOptions: PublisherOptions = PublisherOptions()): Publisher { + return resolvePublisher(keyExpr, publisherOptions) + } /** * Declare a [Subscriber] on the session. * - * The default receiver is a [BlockingQueue], but can be changed with the [Subscriber.Builder.with] functions. - * - * Example: - * + * Example with blocking queue (default receiver): * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/sub")) { - * try (Subscriber>> subscriber = session.declareSubscriber(keyExpr).res()) { - * BlockingQueue> receiver = subscriber.getReceiver(); - * assert receiver != null; - * while (true) { - * Optional sample = receiver.take(); - * if (sample.isEmpty()) { - * break; - * } - * System.out.println(sample.get()); + * try (Session session = Zenoh.open(config)) { + * try (HandlerSubscriber>> subscriber = session.declareSubscriber(keyExpr)) { + * BlockingQueue> receiver = subscriber.getReceiver(); + * assert receiver != null; + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; * } + * System.out.println(wrapper.get()); + * handleSample(wrapper.get()); * } * } * } * ``` * * @param keyExpr The [KeyExpr] the subscriber will be associated to. - * @return A [Subscriber.Builder] with a [BlockingQueue] receiver. + * @return [HandlerSubscriber] with a [BlockingQueue] as a receiver. */ - fun declareSubscriber(keyExpr: KeyExpr): Subscriber.Builder>> = Subscriber.newBuilder(this, keyExpr) + @Throws(ZError::class) + fun declareSubscriber(keyExpr: KeyExpr): HandlerSubscriber>> { + return resolveSubscriberWithHandler( + keyExpr, + BlockingQueueHandler(LinkedBlockingDeque()) + ) + } /** - * Declare a [Queryable] on the session. + * Declare a [Subscriber] on the session using a handler. * - * The default receiver is a [BlockingQueue], but can be changed with the [Queryable.Builder.with] functions. + * Example with a custom handler: + * ```java + * // Example handler that stores the received samples into a queue. + * class QueueHandler implements Handler> { * - * Example: + * final ArrayDeque queue = new ArrayDeque<>(); + * + * @Override + * public void handle(Sample t) { + * queue.add(t); + * } + * + * @Override + * public ArrayDeque receiver() { + * return queue; + * } + * + * @Override + * public void onClose() {} + * } + * + * // ... + * + * try (Session session = Zenoh.open(config)) { + * QueueHandler queueHandler = new QueueHandler(); + * var subscriber = session.declareSubscriber(keyExpr, queueHandler); + * // ... + * } + * ``` + * + * @param R the [handler]'s receiver type. + * @param keyExpr The [KeyExpr] the subscriber will be associated to. + * @param handler The [Handler] to process the incoming [Sample]s received by the subscriber. + * @return A [HandlerSubscriber] with the [handler]'s receiver. + */ + @Throws(ZError::class) + fun declareSubscriber(keyExpr: KeyExpr, handler: Handler): HandlerSubscriber { + return resolveSubscriberWithHandler(keyExpr, handler) + } + + /** + * Declare a [Subscriber] on the session using a callback. + * + * Example with a callback: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/greeting")) { - * System.out.println("Declaring Queryable"); - * try (Queryable>> queryable = session.declareQueryable(keyExpr).res()) { - * BlockingQueue> receiver = queryable.getReceiver(); - * while (true) { - * Optional wrapper = receiver.take(); - * if (wrapper.isEmpty()) { - * break; - * } - * Query query = wrapper.get(); - * System.out.println("Received query at " + query.getSelector()); - * query.reply(keyExpr) - * .success("Hello!") - * .withKind(SampleKind.PUT) - * .withTimeStamp(TimeStamp.getCurrentTime()) - * .res(); - * } + * try (Session session = Zenoh.open(config)) { + * var subscriber = session.declareSubscriber(keyExpr, sample -> System.out.println(sample)); + * // ... + * } + * ``` + * + * @param keyExpr The [KeyExpr] the subscriber will be associated to. + * @param callback [Callback] for handling the incoming samples. + * @return A [CallbackSubscriber]. + */ + @Throws(ZError::class) + fun declareSubscriber(keyExpr: KeyExpr, callback: Callback): CallbackSubscriber { + return resolveSubscriberWithCallback(keyExpr, callback) + } + + /** + * Declare a [Queryable] on the session. + * + * Example using a blocking queue (default receiver): + * ```java + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr); + * var receiver = queryable.getReceiver(); + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; * } + * Query query = wrapper.get(); + * query.reply(query.getKeyExpr(), ZBytes.from("Example reply)); * } * } * ``` * * @param keyExpr The [KeyExpr] the queryable will be associated to. - * @return A [Queryable.Builder] with a [BlockingQueue] receiver. + * @param options Optional [QueryableOptions] for configuring the queryable. + * @return A [HandlerQueryable] with a [BlockingQueue] receiver. + */ + @Throws(ZError::class) + @JvmOverloads + fun declareQueryable( + keyExpr: KeyExpr, + options: QueryableOptions = QueryableOptions() + ): HandlerQueryable>> { + return resolveQueryableWithHandler(keyExpr, BlockingQueueHandler(LinkedBlockingDeque()), options) + } + + /** + * Declare a [Queryable] on the session. + * + * Example using a custom [Handler]: + * ```java + * // Example handler that replies with the amount of queries received. + * class QueryHandler implements Handler { + * + * private Int counter = 0; + * + * @Override + * public void handle(Query query) { + * var keyExpr = query.getKeyExpr(); + * query.reply(keyExpr, ZBytes.from("Reply #" + counter + "!")); + * counter++; + * } + * + * @Override + * public Void receiver() {} + * + * @Override + * public void onClose() {} + * } + * + * // ... + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr, new QueryHandler()); + * //... + * } + * ``` + * + * @param R The type of the [handler]'s receiver. + * @param keyExpr The [KeyExpr] the queryable will be associated to. + * @param handler The [Handler] to handle the incoming queries. + * @param options Optional [QueryableOptions] for configuring the queryable. + * @return A [HandlerQueryable] with the handler's receiver. + */ + @Throws(ZError::class) + @JvmOverloads + fun declareQueryable(keyExpr: KeyExpr, handler: Handler, options: QueryableOptions = QueryableOptions()): HandlerQueryable { + return resolveQueryableWithHandler(keyExpr, handler, options) + } + + /** + * Declare a [Queryable] on the session. + * + * ```java + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr, query -> query.reply(keyExpr, ZBytes.from("Example reply"))); + * //... + * } + * ``` + * + * @param keyExpr The [KeyExpr] the queryable will be associated to. + * @param callback The [Callback] to handle the incoming queries. + * @param options Optional [QueryableOptions] for configuring the queryable. + * @return A [CallbackQueryable]. */ - fun declareQueryable(keyExpr: KeyExpr): Queryable.Builder>> = Queryable.newBuilder(this, keyExpr) + @Throws(ZError::class) + @JvmOverloads + fun declareQueryable(keyExpr: KeyExpr, callback: Callback, options: QueryableOptions = QueryableOptions()): CallbackQueryable { + return resolveQueryableWithCallback(keyExpr, callback, options) + } /** * Declare a [KeyExpr]. @@ -211,22 +330,15 @@ class Session private constructor(private val config: Config) : AutoCloseable { * a queryable, or a publisher will also inform Zenoh of your intent to use their * key expressions repeatedly. * - * Example: - * ```java - * try (Session session = session.open()) { - * try (KeyExpr keyExpr = session.declareKeyExpr("demo/java/example").res()) { - * Publisher publisher = session.declarePublisher(keyExpr).res(); - * // ... - * } - * } - * ``` - * * @param keyExpr The intended Key expression. - * @return A resolvable returning an optimized representation of the passed `keyExpr`. + * @return The declared [KeyExpr]. */ - fun declareKeyExpr(keyExpr: String): Resolvable = Resolvable { - return@Resolvable jniSession?.run { - declareKeyExpr(keyExpr) + @Throws(ZError::class) + fun declareKeyExpr(keyExpr: String): KeyExpr { + return jniSession?.run { + val keyexpr = declareKeyExpr(keyExpr) + declarations.add(keyexpr) + keyexpr } ?: throw sessionClosedException } @@ -239,180 +351,270 @@ class Session private constructor(private val config: Config) : AutoCloseable { * @param keyExpr The key expression to undeclare. * @return A resolvable returning the status of the undeclare operation. */ - fun undeclare(keyExpr: KeyExpr): Resolvable = Resolvable { - return@Resolvable jniSession?.run { + @Throws(ZError::class) + fun undeclare(keyExpr: KeyExpr) { + return jniSession?.run { undeclareKeyExpr(keyExpr) } ?: throw (sessionClosedException) } /** - * Declare a [Get] with a [BlockingQueue] receiver. + * Perform a get query handling the replies through a [BlockingQueue]. * + * Example using the default blocking queue receiver: * ```java - * try (Session session = Session.open()) { - * try (Selector selector = Selector.tryFrom("demo/java/example")) { - * session.get(selector) - * .consolidation(ConsolidationMode.NONE) - * .withValue("Get value example") - * .with(reply -> System.out.println("Received reply " + reply)) - * .res() + * try (Session session = Zenoh.open(config)) { + * System.out.println("Performing Get on '" + selector + "'..."); + * BlockingQueue> receiver = session.get(Selector.from("a/b/c")); + * + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * Reply reply = wrapper.get(); + * System.out.println(reply); * } * } * ``` * - * @param selector The [KeyExpr] to be used for the get operation. - * @return a resolvable [Get.Builder] with a [BlockingQueue] receiver. + * @param selector The [Selector] for the get query. + * @param options Optional [GetOptions] to configure the get query. + * @return A [BlockingQueue] with the received replies. */ - fun get(selector: Selector): Get.Builder>> = Get.newBuilder(this, selector) + @JvmOverloads + @Throws(ZError::class) + fun get(selector: IntoSelector, options: GetOptions = GetOptions()): BlockingQueue> { + val handler = BlockingQueueHandler(LinkedBlockingDeque()) + return resolveGetWithHandler( + selector, + handler, + options + ) + } /** - * Declare a [Get] with a [BlockingQueue] receiver as default. + * Perform a get query handling the replies through a [Handler]. * + * Example using a custom handler: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/example")) { - * session.get(keyExpr) - * .consolidation(ConsolidationMode.NONE) - * .withValue("Get value example") - * .with(reply -> System.out.println("Received reply " + reply)) - * .res() + * // Example handler that prints the replies along with a counter: + * class GetHandler implements Handler { + * + * private Int counter = 0; + * + * @Override + * public void handle(Reply reply) { + * System.out.println("Reply #" + counter + ": " + reply); + * counter++; * } + * + * @Override + * public Void receiver() {} + * + * @Override + * public void onClose() {} + * } + * + * //... + * try (Session session = Zenoh.open(config)) { + * System.out.println("Performing Get on '" + selector + "'..."); + * session.get(Selector.from("a/b/c"), new GetHandler()); + * //... * } * ``` * - * @param keyExpr The [KeyExpr] to be used for the get operation. - * @return a resolvable [Get.Builder] with a [BlockingQueue] receiver. + * @param R The type of the [handler]'s receiver. + * @param selector The [Selector] for the get query. + * @param handler The [Handler] to handle the incoming replies. + * @param options Optional [GetOptions] to configure the query. + * @return The handler's receiver. */ - fun get(keyExpr: KeyExpr): Get.Builder>> = Get.newBuilder(this, Selector(keyExpr)) + @JvmOverloads + @Throws(ZError::class) + fun get(selector: IntoSelector, handler: Handler, options: GetOptions = GetOptions()): R { + return resolveGetWithHandler(selector, handler, options) + } /** - * Declare a [Put] with the provided value on the specified key expression. + * Perform a get query, handling the replies with a [Callback]. * * Example: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/greeting")) { - * session.put(keyExpr, Value("Hello!")) - * .congestionControl(CongestionControl.BLOCK) - * .priority(Priority.REALTIME) - * .kind(SampleKind.PUT) - * .res(); - * System.out.println("Put 'Hello' on " + keyExpr + "."); - * } + * try (Session session = Zenoh.open(config)) { + * session.get(Selector.from("a/b/c"), reply -> System.out.println(reply)); + * //... * } * ``` * - * @param keyExpr The [KeyExpr] to be used for the put operation. - * @param value The [Value] to be put. - * @return A resolvable [Put.Builder]. + * @param selector The [Selector] for the get query. + * @param callback The [Callback] to handle the incoming replies. + * @param options Optional [GetOptions] to configure the query. */ - fun put(keyExpr: KeyExpr, value: Value): Put.Builder = Put.newBuilder(this, keyExpr, value) + @JvmOverloads + @Throws(ZError::class) + fun get(selector: IntoSelector, callback: Callback, options: GetOptions = GetOptions()) { + return resolveGetWithCallback(selector, callback, options) + } /** - * Declare a [Put] with the provided value on the specified key expression. + * Perform a put with the provided [payload] to the specified [keyExpr]. * * Example: * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/greeting")) { - * session.put(keyExpr, "Hello!") - * .congestionControl(CongestionControl.BLOCK) - * .priority(Priority.REALTIME) - * .kind(SampleKind.PUT) - * .res(); - * System.out.println("Put 'Hello' on " + keyExpr + "."); - * } - * } + * session.put(KeyExpr.from("a/b/c"), ZBytes.from("Example payload")); + * //... * ``` * - * @param keyExpr The [KeyExpr] to be used for the put operation. - * @param message The message to be put. - * @return A resolvable [Put.Builder]. + * @param keyExpr The [KeyExpr] for performing the put. + * @param payload The payload to put. + * @param options Optional [PutOptions] to configure the put. */ - fun put(keyExpr: KeyExpr, message: String): Put.Builder = Put.newBuilder(this, keyExpr, Value(message)) + @JvmOverloads + @Throws(ZError::class) + fun put(keyExpr: KeyExpr, payload: IntoZBytes, options: PutOptions = PutOptions()) { + resolvePut(keyExpr, payload, options) + } /** - * Declare a [Delete]. + * Perform a delete operation to the specified [keyExpr]. * - * Example: - * - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example")) { - * session.delete(keyExpr).res(); - * System.out.println("Performed delete on " + keyExpr + "."); - * } - * } - * ``` - * - * @param keyExpr The [KeyExpr] to be used for the delete operation. - * @return a resolvable [Delete.Builder]. + * @param keyExpr The [KeyExpr] for performing the delete operation. + * @param options Optional [DeleteOptions] to configure the delete operation. */ - fun delete(keyExpr: KeyExpr): Delete.Builder = Delete.newBuilder(this, keyExpr) + @JvmOverloads + @Throws(ZError::class) + fun delete(keyExpr: KeyExpr, options: DeleteOptions = DeleteOptions()) { + resolveDelete(keyExpr, options) + } /** Returns if session is open or has been closed. */ - fun isOpen(): Boolean { - return jniSession != null + fun isClosed(): Boolean { + return jniSession == null + } + + /** + * Returns the [SessionInfo] of this session. + */ + fun info(): SessionInfo { + return SessionInfo(this) } - @Throws(SessionException::class) - internal fun resolvePublisher(keyExpr: KeyExpr, qos: QoS): Publisher { + /** + * Obtain a [Liveliness] instance tied to this Zenoh session. + */ + fun liveliness(): Liveliness { + return Liveliness(this) + } + + @Throws(ZError::class) + internal fun resolvePublisher(keyExpr: KeyExpr, options: PublisherOptions): Publisher { return jniSession?.run { - declarePublisher(keyExpr, qos) - } ?: throw(sessionClosedException) + val publisher = declarePublisher(keyExpr, options) + declarations.add(publisher) + publisher + } ?: throw (sessionClosedException) } - @Throws(ZenohException::class) - internal fun resolveSubscriber( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R?, reliability: Reliability - ): Subscriber { + @Throws(ZError::class) + internal fun resolveSubscriberWithHandler( + keyExpr: KeyExpr, handler: Handler + ): HandlerSubscriber { return jniSession?.run { - declareSubscriber(keyExpr, callback, onClose, receiver, reliability) + val subscriber = declareSubscriberWithHandler(keyExpr, handler) + declarations.add(subscriber) + subscriber } ?: throw (sessionClosedException) } - @Throws(ZenohException::class) - internal fun resolveQueryable( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R?, complete: Boolean - ): Queryable { + @Throws(ZError::class) + internal fun resolveSubscriberWithCallback( + keyExpr: KeyExpr, callback: Callback + ): CallbackSubscriber { return jniSession?.run { - declareQueryable(keyExpr, callback, onClose, receiver, complete) + val subscriber = declareSubscriberWithCallback(keyExpr, callback) + declarations.add(subscriber) + subscriber } ?: throw (sessionClosedException) } - @Throws(ZenohException::class) - internal fun resolveGet( - selector: Selector, + @Throws(ZError::class) + internal fun resolveQueryableWithHandler( + keyExpr: KeyExpr, handler: Handler, options: QueryableOptions + ): HandlerQueryable { + return jniSession?.run { + val queryable = declareQueryableWithHandler(keyExpr, handler, options) + declarations.add(queryable) + queryable + } ?: throw (sessionClosedException) + } + + @Throws(ZError::class) + internal fun resolveQueryableWithCallback( + keyExpr: KeyExpr, callback: Callback, options: QueryableOptions + ): CallbackQueryable { + return jniSession?.run { + val queryable = declareQueryableWithCallback(keyExpr, callback, options) + declarations.add(queryable) + queryable + } ?: throw (sessionClosedException) + } + + @Throws(ZError::class) + internal fun resolveGetWithHandler( + selector: IntoSelector, + handler: Handler, + options: GetOptions + ): R { + return jniSession?.performGetWithHandler( + selector, + handler, + options + ) ?: throw sessionClosedException + } + + @Throws(ZError::class) + internal fun resolveGetWithCallback( + selector: IntoSelector, callback: Callback, - onClose: () -> Unit, - receiver: R?, - timeout: Duration, - target: QueryTarget, - consolidation: ConsolidationMode, - value: Value?, - attachment: ByteArray?, - ): R? { - if (jniSession == null) { - throw sessionClosedException - } - return jniSession?.performGet(selector, callback, onClose, receiver, timeout, target, consolidation, value, attachment) + options: GetOptions + ) { + return jniSession?.performGetWithCallback( + selector, + callback, + options + ) ?: throw sessionClosedException + } + + @Throws(ZError::class) + internal fun resolvePut(keyExpr: KeyExpr, payload: IntoZBytes, putOptions: PutOptions) { + jniSession?.run { performPut(keyExpr, payload, putOptions) } + } + + @Throws(ZError::class) + internal fun resolveDelete(keyExpr: KeyExpr, deleteOptions: DeleteOptions) { + jniSession?.run { performDelete(keyExpr, deleteOptions) } + } + + @Throws(ZError::class) + internal fun zid(): ZenohId { + return jniSession?.zid() ?: throw sessionClosedException } - @Throws(ZenohException::class) - internal fun resolvePut(keyExpr: KeyExpr, put: Put) { - jniSession?.run { performPut(keyExpr, put) } + @Throws(ZError::class) + internal fun getPeersId(): List { + return jniSession?.peersZid() ?: throw sessionClosedException } - @Throws(ZenohException::class) - internal fun resolveDelete(keyExpr: KeyExpr, delete: Delete) { - jniSession?.run { performDelete(keyExpr, delete) } + @Throws(ZError::class) + internal fun getRoutersId(): List { + return jniSession?.routersZid() ?: throw sessionClosedException } /** Launches the session through the jni session, returning the [Session] on success. */ - @Throws(SessionException::class) + @Throws(ZError::class) private fun launch(): Session { jniSession!!.open(config) return this } } - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt index ee156130..7a5931f8 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/Zenoh.kt @@ -14,12 +14,131 @@ package io.zenoh +import io.zenoh.Logger.Companion.LOG_ENV +import io.zenoh.exceptions.ZError +import io.zenoh.handlers.BlockingQueueHandler +import io.zenoh.handlers.Callback +import io.zenoh.handlers.Handler +import io.zenoh.jni.JNIScout +import io.zenoh.scouting.* +import java.util.* +import java.util.concurrent.BlockingQueue +import java.util.concurrent.LinkedBlockingDeque + +object Zenoh { + + /** + * Open a [Session] with the provided [Config]. + * + * @param config The configuration for the session. + * @return The [Session] on success. + */ + @JvmStatic + @Throws(ZError::class) + fun open(config: Config): Session { + return Session.open(config) + } + + /** + * Scout for routers and/or peers. + * + * Scout spawns a task that periodically sends scout messages and waits for Hello replies. + * Drop the returned Scout to stop the scouting task or explicitly call [Scout.stop] or [Scout.close]. + * + * @param scoutOptions Optional [ScoutOptions] to configure the scouting. + * @return A [HandlerScout] with a [BlockingQueue] receiver. + */ + @JvmOverloads + @JvmStatic + @Throws(ZError::class) + fun scout(scoutOptions: ScoutOptions = ScoutOptions()): HandlerScout>> { + val handler = BlockingQueueHandler(LinkedBlockingDeque>()) + return JNIScout.scoutWithHandler( + scoutOptions.whatAmI, handler::handle, fun() { handler.onClose() }, + receiver = handler.receiver(), config = scoutOptions.config + ) + } + + /** + * Scout for routers and/or peers. + * + * Scout spawns a task that periodically sends scout messages and waits for Hello replies. + * Drop the returned Scout to stop the scouting task or explicitly call [Scout.stop] or [Scout.close]. + * + * @param R The [handler]'s receiver type. + * @param handler [Handler] to handle the incoming [Hello] messages. + * @param scoutOptions Optional [ScoutOptions] to configure the scouting. + * @return A [HandlerScout] with the handler's receiver. + */ + @JvmOverloads + @JvmStatic + @Throws(ZError::class) + fun scout(handler: Handler, scoutOptions: ScoutOptions = ScoutOptions()): HandlerScout { + return JNIScout.scoutWithHandler( + scoutOptions.whatAmI, handler::handle, fun() { handler.onClose() }, + receiver = handler.receiver(), config = scoutOptions.config + ) + } + + /** + * Scout for routers and/or peers. + * + * Scout spawns a task that periodically sends scout messages and waits for Hello replies. + * Drop the returned Scout to stop the scouting task or explicitly call [Scout.stop] or [Scout.close]. + * + * @param callback [Callback] to handle the incoming [Hello] messages. + * @param scoutOptions Optional [ScoutOptions] to configure the scouting. + * @return A [CallbackScout] with the handler's receiver. + */ + @JvmOverloads + @JvmStatic + @Throws(ZError::class) + fun scout(callback: Callback, scoutOptions: ScoutOptions = ScoutOptions()): CallbackScout { + return JNIScout.scoutWithCallback( + scoutOptions.whatAmI, callback, config = scoutOptions.config + ) + } + + /** + * Initializes the zenoh runtime logger, using rust environment settings. + * E.g.: `RUST_LOG=info` will enable logging at info level. Similarly, you can set the variable to `error` or `debug`. + * + * Note that if the environment variable is not set, then logging will not be enabled. + * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. + * + * @see Logger + */ + @JvmStatic + @Throws(ZError::class) + fun tryInitLogFromEnv() { + val logEnv = System.getenv(LOG_ENV) + if (logEnv != null) { + ZenohLoad + Logger.start(logEnv) + } + } + + /** + * Initializes the zenoh runtime logger, using rust environment settings or the provided fallback level. + * E.g.: `RUST_LOG=info` will enable logging at info level. Similarly, you can set the variable to `error` or `debug`. + * + * Note that if the environment variable is not set, then [fallbackFilter] will be used instead. + * See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. + * + * @param fallbackFilter: The fallback filter if the `RUST_LOG` environment variable is not set. + * @see Logger + */ + @JvmStatic + @Throws(ZError::class) + fun initLogFromEnvOr(fallbackFilter: String) { + ZenohLoad + val logLevelProp = System.getenv(LOG_ENV) + logLevelProp?.let { Logger.start(it) } ?: Logger.start(fallbackFilter) + } +} + /** * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the * log level configuration. */ -internal expect class Zenoh private constructor() { - companion object { - fun load() - } -} +internal expect object ZenohLoad diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/ZenohType.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/ZenohType.kt index 3089ea3c..b8940500 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/ZenohType.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/ZenohType.kt @@ -1,8 +1,7 @@ package io.zenoh /** - * Zenoh type. An empty interface to regroup elements of type [io.zenoh.sample.Sample], - * [io.zenoh.query.Reply] and [io.zenoh.queryable.Query]. + * Zenoh type. * * This kind of elements have in common that they can be received through the Zenoh network. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt new file mode 100644 index 00000000..a4f77815 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/Encoding.kt @@ -0,0 +1,502 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.bytes + +/** + * Default encoding values used by Zenoh. + * + * An encoding has a similar role to Content-type in HTTP: it indicates, when present, how data should be interpreted by the application. + * + * Please note the Zenoh protocol does not impose any encoding value, nor it operates on it. + * It can be seen as some optional metadata that is carried over by Zenoh in such a way the application may perform different operations depending on the encoding value. + * + * A set of associated constants are provided to cover the most common encodings for user convenience. + * This is particularly useful in helping Zenoh to perform additional network optimizations. + */ +class Encoding private constructor( + internal val id: Int, + internal val schema: String? = null, + private val description: String? = null +) { + internal constructor(id: Int, schema: String? = null) : this(id, schema, null) + + companion object { + + /** + * Just some bytes. + * + * Constant alias for string: `"zenoh/bytes"`. + * + * Usually used for types: `ByteArray`, `List`. + */ + @JvmField + val ZENOH_BYTES = Encoding(0, description = "zenoh/bytes") + + /** + * A UTF-8 string. + * + * Constant alias for string: `"zenoh/string"`. + * + * Usually used for type: `String`. + */ + @JvmField + val ZENOH_STRING = Encoding(1, description = "zenoh/string") + + /** + * Zenoh serialized data. + * + * Constant alias for string: `"zenoh/serialized"`. + */ + @JvmField + val ZENOH_SERIALIZED = Encoding(2, description = "zenoh/serialized") + + /** + * An application-specific stream of bytes. + * + * Constant alias for string: `"application/octet-stream"`. + */ + @JvmField + val APPLICATION_OCTET_STREAM = Encoding(3, description = "application/octet-stream") + + /** + * A textual file. + * + * Constant alias for string: `"text/plain"`. + */ + @JvmField + val TEXT_PLAIN = Encoding(4, description = "text/plain") + + /** + * JSON data intended to be consumed by an application. + * + * Constant alias for string: `"application/json"`. + */ + @JvmField + val APPLICATION_JSON = Encoding(5, description = "application/json") + + /** + * JSON data intended to be human readable. + * + * Constant alias for string: `"text/json"`. + */ + @JvmField + val TEXT_JSON = Encoding(6, description = "text/json") + + /** + * A Common Data Representation (CDR)-encoded data. + * + * Constant alias for string: `"application/cdr"`. + */ + @JvmField + val APPLICATION_CDR = Encoding(7, description = "application/cdr") + + /** + * A Concise Binary Object Representation (CBOR)-encoded data. + * + * Constant alias for string: `"application/cbor"`. + */ + @JvmField + val APPLICATION_CBOR = Encoding(8, description = "application/cbor") + + /** + * YAML data intended to be consumed by an application. + * + * Constant alias for string: `"application/yaml"`. + */ + @JvmField + val APPLICATION_YAML = Encoding(9, description = "application/yaml") + + /** + * YAML data intended to be human readable. + * + * Constant alias for string: `"text/yaml"`. + */ + @JvmField + val TEXT_YAML = Encoding(10, description = "text/yaml") + + /** + * JSON5 encoded data that are human readable. + * + * Constant alias for string: `"text/json5"`. + */ + @JvmField + val TEXT_JSON5 = Encoding(11, description = "text/json5") + + /** + * A Python object serialized using [pickle](https://docs.python.org/3/library/pickle.html). + * + * Constant alias for string: `"application/python-serialized-object"`. + */ + @JvmField + val APPLICATION_PYTHON_SERIALIZED_OBJECT = + Encoding(12, description = "application/python-serialized-object") + + /** + * An application-specific protobuf-encoded data. + * + * Constant alias for string: `"application/protobuf"`. + */ + @JvmField + val APPLICATION_PROTOBUF = Encoding(13, description = "application/protobuf") + + /** + * A Java serialized object. + * + * Constant alias for string: `"application/java-serialized-object"`. + */ + @JvmField + val APPLICATION_JAVA_SERIALIZED_OBJECT = + Encoding(14, description = "application/java-serialized-object") + + /** + * OpenMetrics data, commonly used by [Prometheus](https://prometheus.io/). + * + * Constant alias for string: `"application/openmetrics-text"`. + */ + @JvmField + val APPLICATION_OPENMETRICS_TEXT = + Encoding(15, description = "application/openmetrics-text") + + /** + * A Portable Network Graphics (PNG) image. + * + * Constant alias for string: `"image/png"`. + */ + @JvmField + val IMAGE_PNG = Encoding(16, description = "image/png") + + /** + * A Joint Photographic Experts Group (JPEG) image. + * + * Constant alias for string: `"image/jpeg"`. + */ + @JvmField + val IMAGE_JPEG = Encoding(17, description = "image/jpeg") + + /** + * A Graphics Interchange Format (GIF) image. + * + * Constant alias for string: `"image/gif"`. + */ + @JvmField + val IMAGE_GIF = Encoding(18, description = "image/gif") + + /** + * A BitMap (BMP) image. + * + * Constant alias for string: `"image/bmp"`. + */ + @JvmField + val IMAGE_BMP = Encoding(19, description = "image/bmp") + + /** + * A WebP image. + * + * Constant alias for string: `"image/webp"`. + */ + @JvmField + val IMAGE_WEBP = Encoding(20, description = "image/webp") + + /** + * An XML file intended to be consumed by an application. + * + * Constant alias for string: `"application/xml"`. + */ + @JvmField + val APPLICATION_XML = Encoding(21, description = "application/xml") + + /** + * A list of tuples, each consisting of a name and a value. + * + * Constant alias for string: `"application/x-www-form-urlencoded"`. + */ + @JvmField + val APPLICATION_X_WWW_FORM_URLENCODED = + Encoding(22, description = "application/x-www-form-urlencoded") + + /** + * An HTML file. + * + * Constant alias for string: `"text/html"`. + */ + @JvmField + val TEXT_HTML = Encoding(23, description = "text/html") + + /** + * An XML file that is human readable. + * + * Constant alias for string: `"text/xml"`. + */ + @JvmField + val TEXT_XML = Encoding(24, description = "text/xml") + + /** + * A CSS file. + * + * Constant alias for string: `"text/css"`. + */ + @JvmField + val TEXT_CSS = Encoding(25, description = "text/css") + + /** + * A JavaScript file. + * + * Constant alias for string: `"text/javascript"`. + */ + @JvmField + val TEXT_JAVASCRIPT = Encoding(26, description = "text/javascript") + + /** + * A Markdown file. + * + * Constant alias for string: `"text/markdown"`. + */ + @JvmField + val TEXT_MARKDOWN = Encoding(27, description = "text/markdown") + + /** + * A CSV file. + * + * Constant alias for string: `"text/csv"`. + */ + @JvmField + val TEXT_CSV = Encoding(28, description = "text/csv") + + /** + * An application-specific SQL query. + * + * Constant alias for string: `"application/sql"`. + */ + @JvmField + val APPLICATION_SQL = Encoding(29, description = "application/sql") + + /** + * Constrained Application Protocol (CoAP) data intended for CoAP-to-HTTP and HTTP-to-CoAP proxies. + * + * Constant alias for string: `"application/coap-payload"`. + */ + @JvmField + val APPLICATION_COAP_PAYLOAD = Encoding(30, description = "application/coap-payload") + + /** + * Defines a JSON document structure for expressing a sequence of operations to apply to a JSON document. + * + * Constant alias for string: `"application/json-patch+json"`. + */ + @JvmField + val APPLICATION_JSON_PATCH_JSON = Encoding(31, description = "application/json-patch+json") + + /** + * A JSON text sequence consists of any number of JSON texts, all encoded in UTF-8. + * + * Constant alias for string: `"application/json-seq"`. + */ + @JvmField + val APPLICATION_JSON_SEQ = Encoding(32, description = "application/json-seq") + + /** + * A JSONPath defines a string syntax for selecting and extracting JSON values from within a given JSON value. + * + * Constant alias for string: `"application/jsonpath"`. + */ + @JvmField + val APPLICATION_JSONPATH = Encoding(33, description = "application/jsonpath") + + /** + * A JSON Web Token (JWT). + * + * Constant alias for string: `"application/jwt"`. + */ + @JvmField + val APPLICATION_JWT = Encoding(34, description = "application/jwt") + + /** + * An application-specific MPEG-4 encoded data, either audio or video. + * + * Constant alias for string: `"application/mp4"`. + */ + @JvmField + val APPLICATION_MP4 = Encoding(35, description = "application/mp4") + + /** + * A SOAP 1.2 message serialized as XML 1.0. + * + * Constant alias for string: `"application/soap+xml"`. + */ + @JvmField + val APPLICATION_SOAP_XML = Encoding(36, description = "application/soap+xml") + + /** + * A YANG-encoded data commonly used by the Network Configuration Protocol (NETCONF). + * + * Constant alias for string: `"application/yang"`. + */ + @JvmField + val APPLICATION_YANG = Encoding(37, description = "application/yang") + + /** + * A MPEG-4 Advanced Audio Coding (AAC) media. + * + * Constant alias for string: `"audio/aac"`. + */ + @JvmField + val AUDIO_AAC = Encoding(38, description = "audio/aac") + + /** + * A Free Lossless Audio Codec (FLAC) media. + * + * Constant alias for string: `"audio/flac"`. + */ + @JvmField + val AUDIO_FLAC = Encoding(39, description = "audio/flac") + + /** + * An audio codec defined in MPEG-1, MPEG-2, MPEG-4, or registered at the MP4 registration authority. + * + * Constant alias for string: `"audio/mp4"`. + */ + @JvmField + val AUDIO_MP4 = Encoding(40, description = "audio/mp4") + + /** + * An Ogg-encapsulated audio stream. + * + * Constant alias for string: `"audio/ogg"`. + */ + @JvmField + val AUDIO_OGG = Encoding(41, description = "audio/ogg") + + /** + * A Vorbis-encoded audio stream. + * + * Constant alias for string: `"audio/vorbis"`. + */ + @JvmField + val AUDIO_VORBIS = Encoding(42, description = "audio/vorbis") + + /** + * A h261-encoded video stream. + * + * Constant alias for string: `"video/h261"`. + */ + @JvmField + val VIDEO_H261 = Encoding(43, description = "video/h261") + + /** + * A h263-encoded video stream. + * + * Constant alias for string: `"video/h263"`. + */ + @JvmField + val VIDEO_H263 = Encoding(44, description = "video/h263") + + /** + * A h264-encoded video stream. + * + * Constant alias for string: `"video/h264"`. + */ + @JvmField + val VIDEO_H264 = Encoding(45, description = "video/h264") + + /** + * A h265-encoded video stream. + * + * Constant alias for string: `"video/h265"`. + */ + @JvmField + val VIDEO_H265 = Encoding(46, description = "video/h265") + + /** + * A h266-encoded video stream. + * + * Constant alias for string: `"video/h266"`. + */ + @JvmField + val VIDEO_H266 = Encoding(47, description = "video/h266") + + /** + * A video codec defined in MPEG-1, MPEG-2, MPEG-4, or registered at the MP4 registration authority. + * + * Constant alias for string: `"video/mp4"`. + */ + @JvmField + val VIDEO_MP4 = Encoding(48, description = "video/mp4") + + /** + * An Ogg-encapsulated video stream. + * + * Constant alias for string: `"video/ogg"`. + */ + @JvmField + val VIDEO_OGG = Encoding(49, description = "video/ogg") + + /** + * An uncompressed, studio-quality video stream. + * + * Constant alias for string: `"video/raw"`. + */ + @JvmField + val VIDEO_RAW = Encoding(50, description = "video/raw") + + /** + * A VP8-encoded video stream. + * + * Constant alias for string: `"video/vp8"`. + */ + @JvmField + val VIDEO_VP8 = Encoding(51, description = "video/vp8") + + /** + * A VP9-encoded video stream. + * + * Constant alias for string: `"video/vp9"`. + */ + @JvmField + val VIDEO_VP9 = Encoding(52, description = "video/vp9") + + /** + * The default [Encoding] is [ZENOH_BYTES]. + */ + @JvmStatic + fun defaultEncoding() = ZENOH_BYTES + } + + /** + * Set a schema to this encoding. Zenoh does not define what a schema is and its semantics is left to the implementer. + * E.g. a common schema for `text/plain` encoding is `utf-8`. + */ + fun withSchema(schema: String): Encoding { + return Encoding(this.id, schema, this.description) + } + + override fun toString(): String { + val base = description ?: "unknown(${this.id})" + val schemaInfo = schema?.let { ";$it" } ?: "" + return "$base$schemaInfo" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Encoding + + return id == other.id && schema == other.schema + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/IntoZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/IntoZBytes.kt new file mode 100644 index 00000000..a988c34f --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/IntoZBytes.kt @@ -0,0 +1,42 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.bytes + +/** + * IntoZBytes interface. + * + * Classes implementing this interface can be serialized into a ZBytes object. + * + * Example: + * ```java + * class Foo implements IntoZBytes { + * + * private final String content; + * + * Foo(String content) { + * this.content = content; + * } + * + * @NotNull + * @Override + * public ZBytes into() { + * return ZBytes.from(content); + * } + * } + * ``` + */ +interface IntoZBytes { + fun into(): ZBytes +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt new file mode 100644 index 00000000..1fd22d7c --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/bytes/ZBytes.kt @@ -0,0 +1,69 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.bytes + +/** + * ZBytes contains the serialized bytes of user data. + * + * It provides convenient methods to the user for serialization/deserialization. + * + * **NOTE** + * + * Zenoh semantic and protocol take care of sending and receiving bytes + * without restricting the actual data types. Default (de)serializers are provided for + * convenience to the users to deal with primitives data types via a simple + * out-of-the-box encoding. They are NOT by any means the only (de)serializers + * users can use nor a limitation to the types supported by Zenoh. Users are free and + * encouraged to use any data format of their choice like JSON, protobuf, + * flatbuffers, etc. + * + */ +class ZBytes internal constructor(internal val bytes: ByteArray) : IntoZBytes { + + companion object { + + /** + * Creates a [ZBytes] instance from a [String]. + */ + @JvmStatic + fun from(string: String) = ZBytes(string.encodeToByteArray()) + + /** + * Creates a [ZBytes] instance from a [ByteArray]. + */ + @JvmStatic + fun from(bytes: ByteArray) = ZBytes(bytes) + } + + /** Returns the internal byte representation of the [ZBytes]. */ + fun toBytes(): ByteArray = bytes + + /** Attempts to decode the [ZBytes] into a string with UTF-8 encoding. */ + @Throws + fun tryToString(): String = + bytes.decodeToString(throwOnInvalidSequence = true) + + override fun toString(): String = bytes.decodeToString() + + override fun into(): ZBytes = this + + override fun equals(other: Any?) = other is ZBytes && bytes.contentEquals(other.bytes) + + override fun hashCode() = bytes.contentHashCode() +} + +internal fun ByteArray.into(): ZBytes { + return ZBytes(this) +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/SessionException.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt similarity index 62% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/SessionException.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt index 02027e0f..983c9221 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/SessionException.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/WhatAmI.kt @@ -12,11 +12,19 @@ // ZettaScale Zenoh Team, // -package io.zenoh.exceptions +package io.zenoh.config /** - * Session exception. + * WhatAmI * - * This kind of exceptions are thrown from the native code when something goes wrong with a Zenoh session. + * The role of the node sending the `hello` message. */ -class SessionException(message: String?) : ZenohException(message) +enum class WhatAmI(internal val value: Int) { + Router(1), + Peer(2), + Client(4); + + companion object { + internal fun fromInt(value: Int) = entries.first { value == it.value } + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt new file mode 100644 index 00000000..62fefac5 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/config/ZenohId.kt @@ -0,0 +1,40 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.config + +import io.zenoh.jni.JNIZenohId + +/** + * The global unique id of a Zenoh peer. + */ +data class ZenohId internal constructor(internal val bytes: ByteArray) { + + override fun toString(): String { + return JNIZenohId.toStringViaJNI(bytes) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ZenohId + + return bytes.contentEquals(other.bytes) + } + + override fun hashCode(): Int { + return bytes.contentHashCode() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/KeyExprException.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/KeyExprException.kt deleted file mode 100644 index 5836e02c..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/KeyExprException.kt +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.exceptions - -/** - * Key expression exception. - * - * This kind of exceptions are thrown from the native code when something goes wrong regarding a key expression, - * for instance when attempting to create a [io.zenoh.keyexpr.KeyExpr] from a string that does not respect the - * key expression conventions. - */ -class KeyExprException(msg: String) : ZenohException(msg) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZenohException.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt similarity index 82% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZenohException.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt index c94a2dac..c63a19ce 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZenohException.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/ZError.kt @@ -15,6 +15,6 @@ package io.zenoh.exceptions /** - * A Zenoh exception. + * A Zenoh Error. */ -abstract class ZenohException(override val message: String? = null) : Exception() +class ZError(override val message: String? = null): Exception() diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/BlockingQueueHandler.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/BlockingQueueHandler.kt index 1bedd7fa..93a64182 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/BlockingQueueHandler.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/BlockingQueueHandler.kt @@ -21,9 +21,7 @@ import java.util.concurrent.BlockingQueue /** * Blocking queue handler * - * Implementation of a [Handler] with a [BlockingQueue] receiver. This handler is intended to be used - * as the default handler by the [io.zenoh.queryable.Queryable], [io.zenoh.subscriber.Subscriber] and [io.zenoh.query.Get], - * allowing us to send the incoming elements through a [BlockingQueue]. + * Implementation of a [Handler] with a [BlockingQueue] receiver. * * The way to tell no more elements of type [T] will be received is when an empty element is put (see [onClose]). * @@ -31,7 +29,7 @@ import java.util.concurrent.BlockingQueue * @property queue * @constructor Create empty Queue handler */ -class BlockingQueueHandler(private val queue: BlockingQueue>) : Handler>> { +internal class BlockingQueueHandler(private val queue: BlockingQueue>) : Handler>> { override fun handle(t: T) { queue.put(Optional.of(t)) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/Handler.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/Handler.kt index 0647e852..422d6d95 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/Handler.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/handlers/Handler.kt @@ -21,6 +21,9 @@ import io.zenoh.ZenohType * incoming [T] elements. * * **Example**: + * In this example we implement a handler that stores the received elements into an ArrayDeque, + * which can then be retrieved: + * * ```java * public class QueueHandler implements Handler> { * @@ -46,12 +49,12 @@ import io.zenoh.ZenohType * * That `QueueHandler` could then be used as follows, for instance for a subscriber: * ```java - * QueueHandler handler = new QueueHandler(); - * session.declareSubscriber(keyExpr).with(handler).res(); + * var queue = session.declareSubscriber(keyExpr, new QueueHandler()); * ... * ``` + * where the `queue` returned is the receiver from the handler. * - * @param T A receiving [ZenohType], either a [io.zenoh.sample.Sample], a [io.zenoh.query.Reply] or a [io.zenoh.queryable.Query]. + * @param T A receiving [ZenohType]. * @param R An arbitrary receiver. */ interface Handler { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt new file mode 100644 index 00000000..ea278988 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt @@ -0,0 +1,101 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.Config +import io.zenoh.ZenohLoad +import io.zenoh.exceptions.ZError +import java.io.File +import java.nio.file.Path + +internal class JNIConfig(internal val ptr: Long) { + + companion object { + + init { + ZenohLoad + } + + fun loadDefaultConfig(): Config { + val cfgPtr = loadDefaultConfigViaJNI() + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + fun loadConfigFile(path: Path): Config { + val cfgPtr = loadConfigFileViaJNI(path.toString()) + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + fun loadConfigFile(file: File): Config = loadConfigFile(file.toPath()) + + @Throws(ZError::class) + fun loadJsonConfig(rawConfig: String): Config { + val cfgPtr = loadJsonConfigViaJNI(rawConfig) + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + fun loadJson5Config(rawConfig: String): Config { + val cfgPtr = loadJsonConfigViaJNI(rawConfig) + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + fun loadYamlConfig(rawConfig: String): Config { + val cfgPtr = loadYamlConfigViaJNI(rawConfig) + return Config(JNIConfig(cfgPtr)) + } + + @Throws(ZError::class) + private external fun loadDefaultConfigViaJNI(): Long + + @Throws(ZError::class) + private external fun loadConfigFileViaJNI(path: String): Long + + @Throws(ZError::class) + private external fun loadJsonConfigViaJNI(rawConfig: String): Long + + @Throws(ZError::class) + private external fun loadYamlConfigViaJNI(rawConfig: String): Long + + @Throws(ZError::class) + private external fun getIdViaJNI(ptr: Long): ByteArray + + @Throws(ZError::class) + private external fun insertJson5ViaJNI(ptr: Long, key: String, value: String): Long + + /** Frees the underlying native config. */ + private external fun freePtrViaJNI(ptr: Long) + + @Throws(ZError::class) + private external fun getJsonViaJNI(ptr: Long, key: String): String + } + + fun close() { + freePtrViaJNI(ptr) + } + + @Throws(ZError::class) + fun getJson(key: String): String { + return getJsonViaJNI(ptr, key) + } + + @Throws(ZError::class) + fun insertJson5(key: String, value: String) { + insertJson5ViaJNI(this.ptr, key, value) + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt index a8e62cc4..29e419e3 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt @@ -14,25 +14,29 @@ package io.zenoh.jni -import io.zenoh.Zenoh -import io.zenoh.exceptions.ZenohException +import io.zenoh.ZenohLoad +import io.zenoh.exceptions.ZError import io.zenoh.keyexpr.KeyExpr +import io.zenoh.keyexpr.SetIntersectionLevel internal class JNIKeyExpr(internal val ptr: Long) { companion object { - @Throws(ZenohException::class) + init { + ZenohLoad + } + + @Throws(ZError::class) fun tryFrom(keyExpr: String): KeyExpr { - Zenoh.load() // It may happen the zenoh library is not yet loaded when creating a key expression. return KeyExpr(tryFromViaJNI(keyExpr)) } - @Throws(ZenohException::class) + @Throws(ZError::class) fun autocanonize(keyExpr: String): KeyExpr { - Zenoh.load() return KeyExpr(autocanonizeViaJNI(keyExpr)) } + @Throws(ZError::class) fun intersects(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = intersectsViaJNI( keyExprA.jniKeyExpr?.ptr ?: 0, keyExprA.keyExpr, @@ -40,6 +44,7 @@ internal class JNIKeyExpr(internal val ptr: Long) { keyExprB.keyExpr ) + @Throws(ZError::class) fun includes(keyExprA: KeyExpr, keyExprB: KeyExpr): Boolean = includesViaJNI( keyExprA.jniKeyExpr?.ptr ?: 0, keyExprA.keyExpr, @@ -47,17 +52,47 @@ internal class JNIKeyExpr(internal val ptr: Long) { keyExprB.keyExpr ) - @Throws(Exception::class) + @Throws(ZError::class) + fun relationTo(keyExpr: KeyExpr, other: KeyExpr): SetIntersectionLevel { + val intersection = relationToViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + other.jniKeyExpr?.ptr ?: 0, + other.keyExpr + ) + return SetIntersectionLevel.fromInt(intersection) + } + + @Throws(ZError::class) + fun joinViaJNI(keyExpr: KeyExpr, other: String): KeyExpr { + return KeyExpr(joinViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other)) + } + + @Throws(ZError::class) + fun concatViaJNI(keyExpr: KeyExpr, other: String): KeyExpr { + return KeyExpr(concatViaJNI(keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, other)) + } + + @Throws(ZError::class) private external fun tryFromViaJNI(keyExpr: String): String - @Throws(Exception::class) + @Throws(ZError::class) private external fun autocanonizeViaJNI(keyExpr: String): String - @Throws(Exception::class) + @Throws(ZError::class) private external fun intersectsViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean - @Throws(Exception::class) + @Throws(ZError::class) private external fun includesViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Boolean + + @Throws(ZError::class) + private external fun relationToViaJNI(ptrA: Long, keyExprA: String, ptrB: Long, keyExprB: String): Int + + @Throws(ZError::class) + private external fun joinViaJNI(ptrA: Long, keyExprA: String, other: String): String + + @Throws(ZError::class) + private external fun concatViaJNI(ptrA: Long, keyExprA: String, other: String): String } fun close() { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt new file mode 100644 index 00000000..6d6a5aef --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILiveliness.kt @@ -0,0 +1,186 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.into +import io.zenoh.config.ZenohId +import io.zenoh.exceptions.ZError +import io.zenoh.handlers.Callback +import io.zenoh.jni.callbacks.JNIGetCallback +import io.zenoh.jni.callbacks.JNIOnCloseCallback +import io.zenoh.jni.callbacks.JNISubscriberCallback +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.liveliness.LivelinessToken +import io.zenoh.pubsub.CallbackSubscriber +import io.zenoh.pubsub.HandlerSubscriber +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.qos.QoS +import io.zenoh.query.Reply +import io.zenoh.sample.Sample +import io.zenoh.sample.SampleKind +import org.apache.commons.net.ntp.TimeStamp +import java.time.Duration + +internal object JNILiveliness { + + @Throws(ZError::class) + fun get( + jniSession: JNISession, + keyExpr: KeyExpr, + callback: Callback, + receiver: R, + timeout: Duration, + onClose: Runnable + ): R { + val getCallback = JNIGetCallback { + replierId: ByteArray?, + success: Boolean, + keyExpr2: String?, + payload: ByteArray, + encodingId: Int, + encodingSchema: String?, + kind: Int, + timestampNTP64: Long, + timestampIsValid: Boolean, + attachmentBytes: ByteArray?, + express: Boolean, + priority: Int, + congestionControl: Int, + -> + val reply: Reply + if (success) { + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + val sample = Sample( + KeyExpr(keyExpr2!!, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + reply = Reply.Success(replierId?.let { ZenohId(it) }, sample) + } else { + reply = Reply.Error( + replierId?.let { ZenohId(it) }, + payload.into(), + Encoding(encodingId, schema = encodingSchema) + ) + } + callback.run(reply) + } + getViaJNI( + jniSession.sessionPtr.get(), + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + getCallback, + timeout.toMillis(), + onClose::run + ) + return receiver + } + + fun declareToken(jniSession: JNISession, keyExpr: KeyExpr): LivelinessToken { + val ptr = declareTokenViaJNI(jniSession.sessionPtr.get(), keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr) + return LivelinessToken(JNILivelinessToken(ptr)) + } + + fun declareSubscriber( + jniSession: JNISession, + keyExpr: KeyExpr, + callback: Callback, + history: Boolean, + onClose: () -> Unit + ): CallbackSubscriber { + val subCallback = + JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + val sample = Sample( + KeyExpr(keyExpr2, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + callback.run(sample) + } + val ptr = declareSubscriberViaJNI( + jniSession.sessionPtr.get(), + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + subCallback, + history, + onClose + ) + return CallbackSubscriber(keyExpr, JNISubscriber(ptr)) + } + + fun declareSubscriber( + jniSession: JNISession, + keyExpr: KeyExpr, + callback: Callback, + receiver: R, + history: Boolean, + onClose: () -> Unit + ): HandlerSubscriber { + val subCallback = + JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + val sample = Sample( + KeyExpr(keyExpr2, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + callback.run(sample) + } + val ptr = declareSubscriberViaJNI( + jniSession.sessionPtr.get(), + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + subCallback, + history, + onClose + ) + return HandlerSubscriber(keyExpr, JNISubscriber(ptr), receiver) + } + + private external fun getViaJNI( + sessionPtr: Long, + keyExprPtr: Long, + keyExprString: String, + callback: JNIGetCallback, + timeoutMs: Long, + onClose: JNIOnCloseCallback + ) + + private external fun declareTokenViaJNI(sessionPtr: Long, keyExprPtr: Long, keyExprString: String): Long + + private external fun declareSubscriberViaJNI( + sessionPtr: Long, + keyExprPtr: Long, + keyExprString: String, + callback: JNISubscriberCallback, + history: Boolean, + onClose: JNIOnCloseCallback + ): Long +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt new file mode 100644 index 00000000..991c860a --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNILivelinessToken.kt @@ -0,0 +1,12 @@ +package io.zenoh.jni + +internal class JNILivelinessToken(val ptr: Long) { + + fun undeclare() { + undeclareViaJNI(this.ptr) + } + + companion object { + external fun undeclareViaJNI(ptr: Long) + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt index b2eb478f..6e694271 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIPublisher.kt @@ -14,11 +14,12 @@ package io.zenoh.jni -import io.zenoh.exceptions.ZenohException -import io.zenoh.value.Value +import io.zenoh.exceptions.ZError +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes /** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.publication.Publisher]. + * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.pubsub.Publisher]. * * @property ptr: raw pointer to the underlying native Publisher. */ @@ -27,12 +28,14 @@ internal class JNIPublisher(private val ptr: Long) { /** * Put operation. * - * @param value The [Value] to be put. + * @param payload Payload of the put. + * @param encoding Encoding of the payload. * @param attachment Optional attachment. */ - @Throws(ZenohException::class) - fun put(value: Value, attachment: ByteArray?) { - putViaJNI(value.payload, value.encoding.id.ordinal, value.encoding.schema, attachment, ptr) + @Throws(ZError::class) + fun put(payload: IntoZBytes, encoding: Encoding?, attachment: IntoZBytes?) { + val resolvedEncoding = encoding ?: Encoding.defaultEncoding() + putViaJNI(payload.into().bytes, resolvedEncoding.id, resolvedEncoding.schema, attachment?.into()?.bytes, ptr) } /** @@ -40,9 +43,9 @@ internal class JNIPublisher(private val ptr: Long) { * * @param attachment Optional attachment. */ - @Throws(ZenohException::class) - fun delete(attachment: ByteArray?) { - deleteViaJNI(attachment, ptr) + @Throws(ZError::class) + fun delete(attachment: IntoZBytes?) { + deleteViaJNI(attachment?.into()?.bytes, ptr) } /** @@ -54,12 +57,12 @@ internal class JNIPublisher(private val ptr: Long) { freePtrViaJNI(ptr) } - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun putViaJNI( valuePayload: ByteArray, encodingId: Int, encodingSchema: String?, attachment: ByteArray?, ptr: Long ) - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun deleteViaJNI(attachment: ByteArray?, ptr: Long) private external fun freePtrViaJNI(ptr: Long) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt index 20b8fb87..afda43e7 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQuery.kt @@ -14,11 +14,12 @@ package io.zenoh.jni -import io.zenoh.exceptions.ZenohException +import io.zenoh.exceptions.ZError import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.QoS +import io.zenoh.bytes.Encoding +import io.zenoh.qos.QoS +import io.zenoh.bytes.IntoZBytes import io.zenoh.sample.Sample -import io.zenoh.value.Value import org.apache.commons.net.ntp.TimeStamp /** @@ -30,32 +31,29 @@ import org.apache.commons.net.ntp.TimeStamp */ internal class JNIQuery(private val ptr: Long) { - @Throws(ZenohException::class) fun replySuccess(sample: Sample) { val timestampEnabled = sample.timestamp != null replySuccessViaJNI( ptr, sample.keyExpr.jniKeyExpr?.ptr ?: 0, sample.keyExpr.keyExpr, - sample.value.payload, - sample.value.encoding.id.ordinal, - sample.value.encoding.schema, + sample.payload.bytes, + sample.encoding.id, + sample.encoding.schema, timestampEnabled, if (timestampEnabled) sample.timestamp!!.ntpValue() else 0, - sample.attachment, + sample.attachment?.bytes, sample.qos.express, sample.qos.priority.value, sample.qos.congestionControl.value ) } - @Throws(ZenohException::class) - fun replyError(errorValue: Value) { - replyErrorViaJNI(ptr, errorValue.payload, errorValue.encoding.id.ordinal, errorValue.encoding.schema) + fun replyError(error: IntoZBytes, encoding: Encoding) { + replyErrorViaJNI(ptr, error.into().bytes, encoding.id, encoding.schema) } - @Throws(ZenohException::class) - fun replyDelete(keyExpr: KeyExpr, timestamp: TimeStamp?, attachment: ByteArray?, qos: QoS) { + fun replyDelete(keyExpr: KeyExpr, timestamp: TimeStamp?, attachment: IntoZBytes?, qos: QoS) { val timestampEnabled = timestamp != null replyDeleteViaJNI( ptr, @@ -63,7 +61,7 @@ internal class JNIQuery(private val ptr: Long) { keyExpr.keyExpr, timestampEnabled, if (timestampEnabled) timestamp!!.ntpValue() else 0, - attachment, + attachment?.into()?.bytes, qos.express, qos.priority.value, qos.congestionControl.value @@ -74,7 +72,7 @@ internal class JNIQuery(private val ptr: Long) { freePtrViaJNI(ptr) } - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun replySuccessViaJNI( queryPtr: Long, keyExprPtr: Long, @@ -90,7 +88,7 @@ internal class JNIQuery(private val ptr: Long) { qosCongestionControl: Int, ) - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun replyErrorViaJNI( queryPtr: Long, errorValuePayload: ByteArray, @@ -98,7 +96,7 @@ internal class JNIQuery(private val ptr: Long) { encodingSchema: String?, ) - @Throws(ZenohException::class) + @Throws(ZError::class) private external fun replyDeleteViaJNI( queryPtr: Long, keyExprPtr: Long, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt index f17df868..e5f7d3ce 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIQueryable.kt @@ -15,7 +15,7 @@ package io.zenoh.jni /** - * Adapter class to handle the interactions with Zenoh through JNI for a [Queryable] + * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.query.Queryable] * * @property ptr: raw pointer to the underlying native Queryable. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt new file mode 100644 index 00000000..f2e1c49c --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIScout.kt @@ -0,0 +1,87 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.jni + +import io.zenoh.Config +import io.zenoh.ZenohLoad +import io.zenoh.exceptions.ZError +import io.zenoh.handlers.Callback +import io.zenoh.jni.callbacks.JNIScoutCallback +import io.zenoh.config.ZenohId +import io.zenoh.scouting.Hello +import io.zenoh.config.WhatAmI +import io.zenoh.jni.callbacks.JNIOnCloseCallback +import io.zenoh.scouting.CallbackScout +import io.zenoh.scouting.HandlerScout + +/** + * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.scouting.Scout] + * + * @property ptr: raw pointer to the underlying native scout. + */ +internal class JNIScout(private val ptr: Long) { + + companion object { + + init { + ZenohLoad + } + + @Throws(ZError::class) + fun scoutWithHandler( + whatAmI: Set, + callback: Callback, + onClose: () -> Unit, + config: Config?, + receiver: R + ): HandlerScout { + val scoutCallback = JNIScoutCallback { whatAmI2: Int, id: ByteArray, locators: List -> + callback.run(Hello(WhatAmI.fromInt(whatAmI2), ZenohId(id), locators)) + } + val binaryWhatAmI: Int = whatAmI.map { it.value }.reduce { acc, it -> acc or it } + val ptr = scoutViaJNI(binaryWhatAmI, scoutCallback, onClose,config?.jniConfig?.ptr ?: 0) + return HandlerScout(JNIScout(ptr), receiver) + } + + @Throws(ZError::class) + fun scoutWithCallback( + whatAmI: Set, + callback: Callback, + config: Config?, + ): CallbackScout { + val scoutCallback = JNIScoutCallback { whatAmI2: Int, id: ByteArray, locators: List -> + callback.run(Hello(WhatAmI.fromInt(whatAmI2), ZenohId(id), locators)) + } + val binaryWhatAmI: Int = whatAmI.map { it.value }.reduce { acc, it -> acc or it } + val ptr = scoutViaJNI(binaryWhatAmI, scoutCallback, fun() {},config?.jniConfig?.ptr ?: 0) + return CallbackScout(JNIScout(ptr)) + } + + @Throws(ZError::class) + private external fun scoutViaJNI( + whatAmI: Int, + callback: JNIScoutCallback, + onClose: JNIOnCloseCallback, + configPtr: Long, + ): Long + + @Throws(ZError::class) + external fun freePtrViaJNI(ptr: Long) + } + + fun close() { + freePtrViaJNI(ptr) + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index e12b2948..8604bc24 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -15,130 +15,202 @@ package io.zenoh.jni import io.zenoh.* -import io.zenoh.prelude.Encoding -import io.zenoh.prelude.Encoding.ID -import io.zenoh.exceptions.SessionException -import io.zenoh.exceptions.ZenohException +import io.zenoh.bytes.Encoding +import io.zenoh.exceptions.ZError import io.zenoh.handlers.Callback import io.zenoh.jni.callbacks.JNIOnCloseCallback import io.zenoh.jni.callbacks.JNIGetCallback import io.zenoh.jni.callbacks.JNIQueryableCallback import io.zenoh.jni.callbacks.JNISubscriberCallback import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.* -import io.zenoh.protocol.ZenohID -import io.zenoh.publication.Delete -import io.zenoh.publication.Publisher -import io.zenoh.publication.Put +import io.zenoh.bytes.IntoZBytes +import io.zenoh.config.ZenohId +import io.zenoh.bytes.into +import io.zenoh.Config +import io.zenoh.handlers.Handler +import io.zenoh.pubsub.* +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.qos.QoS import io.zenoh.query.* -import io.zenoh.queryable.Query -import io.zenoh.queryable.Queryable import io.zenoh.sample.Sample -import io.zenoh.selector.Selector -import io.zenoh.subscriber.Reliability -import io.zenoh.subscriber.Subscriber -import io.zenoh.value.Value +import io.zenoh.sample.SampleKind import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration import java.util.concurrent.atomic.AtomicLong /** Adapter class to handle the communication with the Zenoh JNI code for a [Session]. */ internal class JNISession { + companion object { + init { + ZenohLoad + } + } + /* Pointer to the underlying Rust zenoh session. */ - private var sessionPtr: AtomicLong = AtomicLong(0) + internal var sessionPtr: AtomicLong = AtomicLong(0) - @Throws(ZenohException::class) + @Throws(ZError::class) fun open(config: Config) { - config.jsonConfig?.let { jsonConfig -> - sessionPtr.set(openSessionWithJsonConfigViaJNI(jsonConfig.toString())) - } ?: run { - sessionPtr.set(openSessionViaJNI(config.path?.toString())) - } + val session = openSessionViaJNI(config.jniConfig.ptr) + sessionPtr.set(session) } - @Throws(ZenohException::class) fun close() { closeSessionViaJNI(sessionPtr.get()) } - @Throws(ZenohException::class) - fun declarePublisher(keyExpr: KeyExpr, qos: QoS): Publisher { + @Throws(ZError::class) + fun declarePublisher(keyExpr: KeyExpr, publisherOptions: PublisherOptions): Publisher { val publisherRawPtr = declarePublisherViaJNI( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), - qos.congestionControl.value, - qos.priority.value, - qos.express + publisherOptions.congestionControl.value, + publisherOptions.priority.value, + publisherOptions.express, + publisherOptions.reliability.ordinal ) return Publisher( keyExpr, - qos, + publisherOptions.congestionControl, + publisherOptions.priority, + publisherOptions.encoding, JNIPublisher(publisherRawPtr), ) } - @Throws(ZenohException::class) - fun declareSubscriber( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R?, reliability: Reliability - ): Subscriber { + @Throws(ZError::class) + fun declareSubscriberWithHandler( + keyExpr: KeyExpr, handler: Handler + ): HandlerSubscriber { val subCallback = - JNISubscriberCallback { keyExpr2, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> + JNISubscriberCallback { keyExpr1, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null val sample = Sample( - KeyExpr(keyExpr2, null), - Value(payload, Encoding(ID.fromId(encodingId)!!, encodingSchema)), + KeyExpr(keyExpr1, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), SampleKind.fromInt(kind), timestamp, - QoS(express, congestionControl, priority), - attachmentBytes + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + handler.handle(sample) + } + val subscriberRawPtr = declareSubscriberViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), subCallback, handler::onClose + ) + return HandlerSubscriber(keyExpr, JNISubscriber(subscriberRawPtr), handler.receiver()) + } + + @Throws(ZError::class) + fun declareSubscriberWithCallback( + keyExpr: KeyExpr, callback: Callback + ): CallbackSubscriber { + val subCallback = + JNISubscriberCallback { keyExpr1, payload, encodingId, encodingSchema, kind, timestampNTP64, timestampIsValid, attachmentBytes, express: Boolean, priority: Int, congestionControl: Int -> + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + val sample = Sample( + KeyExpr(keyExpr1, null), + payload.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() ) callback.run(sample) } val subscriberRawPtr = declareSubscriberViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), subCallback, onClose, reliability.ordinal + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr.get(), + subCallback, + fun() {} ) - return Subscriber(keyExpr, receiver, JNISubscriber(subscriberRawPtr)) + return CallbackSubscriber(keyExpr, JNISubscriber(subscriberRawPtr)) } - @Throws(ZenohException::class) - fun declareQueryable( - keyExpr: KeyExpr, callback: Callback, onClose: () -> Unit, receiver: R?, complete: Boolean - ): Queryable { + @Throws(ZError::class) + fun declareQueryableWithCallback( + keyExpr: KeyExpr, callback: Callback, config: QueryableOptions + ): CallbackQueryable { val queryCallback = - JNIQueryableCallback { keyExprStr: String, selectorParams: String, withValue: Boolean, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long -> + JNIQueryableCallback { keyExpr1: String, selectorParams: String, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long -> val jniQuery = JNIQuery(queryPtr) - val keyExpr2 = KeyExpr(keyExprStr, null) - val selector = Selector(keyExpr2, selectorParams) - val value: Value? = - if (withValue) Value(payload!!, Encoding(ID.fromId(encodingId)!!, encodingSchema)) else null - val query = Query(keyExpr2, selector, value, attachmentBytes, jniQuery) + val keyExpr2 = KeyExpr(keyExpr1, null) + val selector = if (selectorParams.isEmpty()) { + Selector(keyExpr2) + } else { + Selector(keyExpr2, Parameters.from(selectorParams)) + } + val query = Query( + keyExpr2, + selector, + payload?.into(), + payload?.let { Encoding(encodingId, schema = encodingSchema) }, + attachmentBytes?.into(), + jniQuery + ) callback.run(query) } val queryableRawPtr = declareQueryableViaJNI( - keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), queryCallback, onClose, complete + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr.get(), + queryCallback, + fun() {}, + config.complete + ) + return CallbackQueryable(keyExpr, JNIQueryable(queryableRawPtr)) + } + + @Throws(ZError::class) + fun declareQueryableWithHandler( + keyExpr: KeyExpr, handler: Handler, config: QueryableOptions + ): HandlerQueryable { + val queryCallback = + JNIQueryableCallback { keyExpr1: String, selectorParams: String, payload: ByteArray?, encodingId: Int, encodingSchema: String?, attachmentBytes: ByteArray?, queryPtr: Long -> + val jniQuery = JNIQuery(queryPtr) + val keyExpr2 = KeyExpr(keyExpr1, null) + val selector = if (selectorParams.isEmpty()) { + Selector(keyExpr2) + } else { + Selector(keyExpr2, Parameters.from(selectorParams)) + } + val query = Query( + keyExpr2, + selector, + payload?.into(), + payload?.let { Encoding(encodingId, schema = encodingSchema) }, + attachmentBytes?.into(), + jniQuery + ) + handler.handle(query) + } + val queryableRawPtr = declareQueryableViaJNI( + keyExpr.jniKeyExpr?.ptr ?: 0, + keyExpr.keyExpr, + sessionPtr.get(), + queryCallback, + handler::onClose, + config.complete ) - return Queryable(keyExpr, receiver, JNIQueryable(queryableRawPtr)) + return HandlerQueryable(keyExpr, JNIQueryable(queryableRawPtr), handler.receiver()) } - @Throws(ZenohException::class) - fun performGet( - selector: Selector, + @Throws(ZError::class) + fun performGetWithCallback( + intoSelector: IntoSelector, callback: Callback, - onClose: () -> Unit, - receiver: R?, - timeout: Duration, - target: QueryTarget, - consolidation: ConsolidationMode, - value: Value?, - attachment: ByteArray? - ): R? { + options: GetOptions + ) { val getCallback = JNIGetCallback { - replierId: String?, + replierId: ByteArray?, success: Boolean, keyExpr: String?, - payload: ByteArray, + payload1: ByteArray, encodingId: Int, encodingSchema: String?, kind: Int, @@ -152,133 +224,212 @@ internal class JNISession { val reply: Reply if (success) { val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null - when (SampleKind.fromInt(kind)) { - SampleKind.PUT -> { - val sample = Sample( - KeyExpr(keyExpr!!, null), - Value(payload, Encoding(ID.fromId(encodingId)!!, encodingSchema)), - SampleKind.fromInt(kind), - timestamp, - QoS(express, congestionControl, priority), - attachmentBytes - ) - reply = Reply.Success(replierId?.let { ZenohID(it) }, sample) - } - - SampleKind.DELETE -> { - reply = Reply.Delete( - replierId?.let { ZenohID(it) }, - KeyExpr(keyExpr!!, null), - timestamp, - attachmentBytes, - QoS(express, congestionControl, priority) - ) - } - } + val sample = Sample( + KeyExpr(keyExpr!!, null), + payload1.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + reply = Reply.Success(replierId?.let { ZenohId(it) }, sample) } else { - reply = Reply.Error(replierId?.let { ZenohID(it) }, Value(payload, Encoding(ID.fromId(encodingId)!!, encodingSchema))) + reply = Reply.Error( + replierId?.let { ZenohId(it) }, + payload1.into(), + Encoding(encodingId, schema = encodingSchema) + ) } callback.run(reply) } + val selector = intoSelector.into() getViaJNI( selector.keyExpr.jniKeyExpr?.ptr ?: 0, selector.keyExpr.keyExpr, - selector.parameters, + selector.parameters.toString(), sessionPtr.get(), getCallback, - onClose, - timeout.toMillis(), - target.ordinal, - consolidation.ordinal, - attachment, - value != null, - value?.payload, - value?.encoding?.id?.ordinal ?: 0, - value?.encoding?.schema + fun() {}, + options.timeout.toMillis(), + options.target.ordinal, + options.consolidation.ordinal, + options.attachment?.into()?.bytes, + options.payload?.into()?.bytes, + options.encoding?.id ?: Encoding.defaultEncoding().id, + options.encoding?.schema ) - return receiver } - @Throws(ZenohException::class) + @Throws(ZError::class) + fun performGetWithHandler( + intoSelector: IntoSelector, + handler: Handler, + options: GetOptions + ): R { + val getCallback = JNIGetCallback { + replierId: ByteArray?, + success: Boolean, + keyExpr: String?, + payload1: ByteArray, + encodingId: Int, + encodingSchema: String?, + kind: Int, + timestampNTP64: Long, + timestampIsValid: Boolean, + attachmentBytes: ByteArray?, + express: Boolean, + priority: Int, + congestionControl: Int, + -> + val reply: Reply + if (success) { + val timestamp = if (timestampIsValid) TimeStamp(timestampNTP64) else null + val sample = Sample( + KeyExpr(keyExpr!!, null), + payload1.into(), + Encoding(encodingId, schema = encodingSchema), + SampleKind.fromInt(kind), + timestamp, + QoS(CongestionControl.fromInt(congestionControl), Priority.fromInt(priority), express), + attachmentBytes?.into() + ) + reply = Reply.Success(replierId?.let { ZenohId(it) }, sample) + } else { + reply = Reply.Error( + replierId?.let { ZenohId(it) }, + payload1.into(), + Encoding(encodingId, schema = encodingSchema) + ) + } + handler.handle(reply) + } + + val selector = intoSelector.into() + getViaJNI( + selector.keyExpr.jniKeyExpr?.ptr ?: 0, + selector.keyExpr.keyExpr, + selector.parameters.toString(), + sessionPtr.get(), + getCallback, + handler::onClose, + options.timeout.toMillis(), + options.target.ordinal, + options.consolidation.ordinal, + options.attachment?.into()?.bytes, + options.payload?.into()?.bytes, + options.encoding?.id ?: Encoding.defaultEncoding().id, + options.encoding?.schema + ) + return handler.receiver() + } + + + @Throws(ZError::class) fun declareKeyExpr(keyExpr: String): KeyExpr { val ptr = declareKeyExprViaJNI(sessionPtr.get(), keyExpr) return KeyExpr(keyExpr, JNIKeyExpr(ptr)) } - @Throws(ZenohException::class) + @Throws(ZError::class) fun undeclareKeyExpr(keyExpr: KeyExpr) { keyExpr.jniKeyExpr?.run { undeclareKeyExprViaJNI(sessionPtr.get(), this.ptr) keyExpr.jniKeyExpr = null - } ?: throw SessionException("Attempting to undeclare a non declared key expression.") + } ?: throw ZError("Attempting to undeclare a non declared key expression.") } - @Throws(Exception::class) + @Throws(ZError::class) fun performPut( keyExpr: KeyExpr, - put: Put, + payload: IntoZBytes, + options: PutOptions, ) { + val encoding = options.encoding ?: Encoding.defaultEncoding() putViaJNI( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), - put.value.payload, - put.value.encoding.id.ordinal, - put.value.encoding.schema, - put.qos.congestionControl.value, - put.qos.priority.value, - put.qos.express, - put.attachment + payload.into().bytes, + encoding.id, + encoding.schema, + options.congestionControl.value, + options.priority.value, + options.express, + options.attachment?.into()?.bytes, + options.reliability.ordinal ) } - @Throws(Exception::class) + @Throws(ZError::class) fun performDelete( keyExpr: KeyExpr, - delete: Delete, + options: DeleteOptions, ) { deleteViaJNI( keyExpr.jniKeyExpr?.ptr ?: 0, keyExpr.keyExpr, sessionPtr.get(), - delete.qos.congestionControl.value, - delete.qos.priority.value, - delete.qos.express, - delete.attachment + options.congestionControl.value, + options.priority.value, + options.express, + options.attachment?.into()?.bytes, + options.reliability.ordinal ) } - @Throws(Exception::class) - private external fun openSessionViaJNI(configFilePath: String?): Long + @Throws(ZError::class) + fun zid(): ZenohId { + return ZenohId(getZidViaJNI(sessionPtr.get())) + } + + @Throws(ZError::class) + fun peersZid(): List { + return getPeersZidViaJNI(sessionPtr.get()).map { ZenohId(it) } + } + + @Throws(ZError::class) + fun routersZid(): List { + return getRoutersZidViaJNI(sessionPtr.get()).map { ZenohId(it) } + } + + @Throws(ZError::class) + private external fun getZidViaJNI(ptr: Long): ByteArray + + @Throws(ZError::class) + private external fun getPeersZidViaJNI(ptr: Long): List - @Throws(Exception::class) - private external fun openSessionWithJsonConfigViaJNI(jsonConfig: String?): Long + @Throws(ZError::class) + private external fun getRoutersZidViaJNI(ptr: Long): List - @Throws(Exception::class) + @Throws(ZError::class) + private external fun openSessionViaJNI(configPtr: Long): Long + + @Throws(ZError::class) private external fun closeSessionViaJNI(ptr: Long) - @Throws(Exception::class) + @Throws(ZError::class) private external fun declarePublisherViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, congestionControl: Int, priority: Int, - express: Boolean + express: Boolean, + reliability: Int ): Long - @Throws(Exception::class) + @Throws(ZError::class) private external fun declareSubscriberViaJNI( keyExprPtr: Long, keyExprString: String, sessionPtr: Long, callback: JNISubscriberCallback, onClose: JNIOnCloseCallback, - reliability: Int ): Long - @Throws(Exception::class) + @Throws(ZError::class) private external fun declareQueryableViaJNI( keyExprPtr: Long, keyExprString: String, @@ -288,17 +439,17 @@ internal class JNISession { complete: Boolean ): Long - @Throws(Exception::class) + @Throws(ZError::class) private external fun declareKeyExprViaJNI(sessionPtr: Long, keyExpr: String): Long - @Throws(Exception::class) + @Throws(ZError::class) private external fun undeclareKeyExprViaJNI(sessionPtr: Long, keyExprPtr: Long) - @Throws(Exception::class) + @Throws(ZError::class) private external fun getViaJNI( keyExprPtr: Long, keyExprString: String, - selectorParams: String, + selectorParams: String?, sessionPtr: Long, callback: JNIGetCallback, onClose: JNIOnCloseCallback, @@ -306,13 +457,12 @@ internal class JNISession { target: Int, consolidation: Int, attachmentBytes: ByteArray?, - withValue: Boolean, payload: ByteArray?, encodingId: Int, encodingSchema: String?, ) - @Throws(Exception::class) + @Throws(ZError::class) private external fun putViaJNI( keyExprPtr: Long, keyExprString: String, @@ -323,10 +473,11 @@ internal class JNISession { congestionControl: Int, priority: Int, express: Boolean, - attachmentBytes: ByteArray? + attachmentBytes: ByteArray?, + reliability: Int ) - @Throws(Exception::class) + @Throws(ZError::class) private external fun deleteViaJNI( keyExprPtr: Long, keyExprString: String, @@ -334,6 +485,7 @@ internal class JNISession { congestionControl: Int, priority: Int, express: Boolean, - attachmentBytes: ByteArray? + attachmentBytes: ByteArray?, + reliability: Int ) } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt index 73bd2dad..1bb80543 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNISubscriber.kt @@ -15,7 +15,7 @@ package io.zenoh.jni /** - * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.subscriber.Subscriber] + * Adapter class to handle the interactions with Zenoh through JNI for a [io.zenoh.pubsub.Subscriber] * * @property ptr: raw pointer to the underlying native Subscriber. */ diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/Resolvable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt similarity index 73% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/Resolvable.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt index bc0012fe..53ecb5dc 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/Resolvable.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/JNIZenohId.kt @@ -12,12 +12,16 @@ // ZettaScale Zenoh Team, // -package io.zenoh +package io.zenoh.jni -/** - * A resolvable function interface meant to be used by Zenoh builders. - */ -fun interface Resolvable { +import io.zenoh.ZenohLoad + +internal object JNIZenohId { + + init { + ZenohLoad + } + + external fun toStringViaJNI(bytes: ByteArray): String - fun res(): R } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt index b78fc33f..ae978471 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIGetCallback.kt @@ -17,7 +17,7 @@ package io.zenoh.jni.callbacks internal fun interface JNIGetCallback { fun run( - replierId: String?, + replierId: ByteArray?, success: Boolean, keyExpr: String?, payload: ByteArray, diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt index 477d0dd0..31f5885f 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIQueryableCallback.kt @@ -15,14 +15,11 @@ package io.zenoh.jni.callbacks internal fun interface JNIQueryableCallback { - fun run( - keyExpr: String, - selectorParams: String, - withValue: Boolean, - payload: ByteArray?, - encodingId: Int, - encodingSchema: String?, - attachmentBytes: ByteArray?, - queryPtr: Long - ) + fun run(keyExpr: String, + selectorParams: String, + payload: ByteArray?, + encodingId: Int, + encodingSchema: String?, + attachmentBytes: ByteArray?, + queryPtr: Long) } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/protocol/ZenohID.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt similarity index 76% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/protocol/ZenohID.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt index 953fb226..0a8b20e9 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/protocol/ZenohID.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/jni/callbacks/JNIScoutCallback.kt @@ -12,9 +12,9 @@ // ZettaScale Zenoh Team, // -package io.zenoh.protocol +package io.zenoh.jni.callbacks -/** - * The global unique id of a Zenoh peer. - */ -class ZenohID(val id: String) +internal fun interface JNIScoutCallback { + + fun run(whatAmI: Int, zid: ByteArray, locators: List) +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt index ec138385..b5fe6110 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/KeyExpr.kt @@ -14,10 +14,12 @@ package io.zenoh.keyexpr -import io.zenoh.Resolvable import io.zenoh.Session -import io.zenoh.exceptions.KeyExprException +import io.zenoh.session.SessionDeclaration +import io.zenoh.exceptions.ZError import io.zenoh.jni.JNIKeyExpr +import io.zenoh.query.IntoSelector +import io.zenoh.query.Selector /** * # Address space @@ -56,11 +58,9 @@ import io.zenoh.jni.JNIKeyExpr * As an alternative, employing a try-with-resources pattern using Kotlin's `use` block is recommended. This approach * ensures that [close] is automatically called, safely managing the lifecycle of the [KeyExpr] instance. * - * @param keyExpr The string representation of the key expression. - * @param jniKeyExpr An optional [JNIKeyExpr] instance, present when the key expression was declared through [Session.declareKeyExpr], - * it represents the native instance of the key expression. */ -class KeyExpr internal constructor(internal val keyExpr: String, internal var jniKeyExpr: JNIKeyExpr? = null): AutoCloseable { +class KeyExpr internal constructor(internal val keyExpr: String, internal var jniKeyExpr: JNIKeyExpr? = null): AutoCloseable, IntoSelector, + SessionDeclaration { companion object { @@ -74,10 +74,10 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * * @param keyExpr The intended key expression as a string. * @return The [KeyExpr] in case of success. - * @throws KeyExprException in the case of failure. + * @throws ZError in the case of failure. */ @JvmStatic - @Throws(KeyExprException::class) + @Throws(ZError::class) fun tryFrom(keyExpr: String): KeyExpr { return JNIKeyExpr.tryFrom(keyExpr) } @@ -90,10 +90,10 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * * @param keyExpr The intended key expression as a string. * @return The canonized [KeyExpr]. - * @throws KeyExprException in the case of failure. + * @throws ZError in the case of failure. */ @JvmStatic - @Throws(KeyExprException::class) + @Throws(ZError::class) fun autocanonize(keyExpr: String): KeyExpr { return JNIKeyExpr.autocanonize(keyExpr) } @@ -104,6 +104,7 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * defined by `this` and `other`. * Will return false as well if the key expression is not valid anymore. */ + @Throws(ZError::class) fun intersects(other: KeyExpr): Boolean { return JNIKeyExpr.intersects(this, other) } @@ -113,25 +114,37 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn * defined by `this`. * Will return false as well if the key expression is not valid anymore. */ + @Throws(ZError::class) fun includes(other: KeyExpr): Boolean { return JNIKeyExpr.includes(this, other) } /** - * Undeclare the key expression if it was previously declared on the specified [session]. - * - * @param session The session from which the key expression was previously declared. - * @return An empty [Resolvable]. + * Returns the relation between 'this' and other from 'this''s point of view ([SetIntersectionLevel.INCLUDES] + * signifies that self includes other). Note that this is slower than [intersects] and [includes], + * so you should favor these methods for most applications. + */ + @Throws(ZError::class) + fun relationTo(other: KeyExpr): SetIntersectionLevel { + return JNIKeyExpr.relationTo(this, other) + } + + /** + * Joins both sides, inserting a / in between them. + * This should be your preferred method when concatenating path segments. */ - fun undeclare(session: Session): Resolvable { - return session.undeclare(this) + @Throws(ZError::class) + fun join(other: String): KeyExpr { + return JNIKeyExpr.joinViaJNI(this, other) } /** - * Returns true if the [KeyExpr] has still associated a native key expression allowing it to perform operations. + * Performs string concatenation and returns the result as a KeyExpr if possible. + * You should probably prefer [join] as Zenoh may then take advantage of the hierarchical separation it inserts. */ - fun isValid(): Boolean { - return jniKeyExpr != null + @Throws(ZError::class) + fun concat(other: String): KeyExpr { + return JNIKeyExpr.concatViaJNI(this, other) } override fun toString(): String { @@ -139,13 +152,26 @@ class KeyExpr internal constructor(internal val keyExpr: String, internal var jn } /** - * Closes the key expression. Operations performed on this key expression won't be valid anymore after this call. + * Equivalent to [undeclare]. This function is automatically called when using try with resources. + * + * @see undeclare */ override fun close() { + undeclare() + } + + /** + * If the key expression was declared from a [Session], then [undeclare] frees the native key expression associated + * to this instance. The KeyExpr instance is downgraded into a normal KeyExpr, which still allows performing + * operations on it, but without the inner optimizations. + */ + override fun undeclare() { jniKeyExpr?.close() jniKeyExpr = null } + override fun into(): Selector = Selector(this) + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/IntoKeyExpr.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt similarity index 56% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/IntoKeyExpr.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt index 74ab2d80..981cc4b1 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/IntoKeyExpr.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/keyexpr/SetIntersectionLevel.kt @@ -14,13 +14,18 @@ package io.zenoh.keyexpr -import io.zenoh.exceptions.KeyExprException +/** + * The possible relations between two sets. + * + * Note that [EQUALS] implies [INCLUDES], which itself implies [INTERSECTS]. + */ +enum class SetIntersectionLevel(internal val value: Int) { + DISJOINT(0), + INTERSECTS(1), + INCLUDES(2), + EQUALS(3); -@Throws(KeyExprException::class) -fun String.intoKeyExpr(): KeyExpr { - if (this.isEmpty()) { - throw(KeyExprException("Attempting to create a KeyExpr from an empty string.")) + companion object { + internal fun fromInt(value: Int) = SetIntersectionLevel.entries.first { it.value == value } } - return KeyExpr.autocanonize(this) } - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt new file mode 100644 index 00000000..96711557 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/Liveliness.kt @@ -0,0 +1,198 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.liveliness + +import io.zenoh.Session +import io.zenoh.exceptions.ZError +import io.zenoh.handlers.BlockingQueueHandler +import io.zenoh.handlers.Callback +import io.zenoh.handlers.Handler +import io.zenoh.jni.JNILiveliness +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.pubsub.CallbackSubscriber +import io.zenoh.pubsub.HandlerSubscriber +import io.zenoh.pubsub.Subscriber +import io.zenoh.query.Reply +import io.zenoh.sample.Sample +import java.time.Duration +import java.util.* +import java.util.concurrent.BlockingQueue +import java.util.concurrent.LinkedBlockingDeque + +/** + * A structure with functions to declare a [LivelinessToken], + * query existing [LivelinessToken]s and subscribe to liveliness changes. + * + * A [LivelinessToken] is a token which liveliness is tied + * to the Zenoh [Session] and can be monitored by remote applications. + * + * The [Liveliness] instance can be obtained with the [Session.liveliness] function + * of the [Session] instance. + */ +class Liveliness internal constructor(private val session: Session) { + + /** + * Create a LivelinessToken for the given key expression. + */ + @Throws(ZError::class) + fun declareToken(keyExpr: KeyExpr): LivelinessToken { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.declareToken(jniSession, keyExpr) + } + + /** + * Query the liveliness tokens with matching key expressions. + * + * @param keyExpr The [KeyExpr] for the query. + * @param timeout Optional timeout of the query, defaults to 10 secs. + */ + @JvmOverloads + @Throws(ZError::class) + fun get( + keyExpr: KeyExpr, + timeout: Duration = Duration.ofMillis(10000), + ): BlockingQueue> { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + val handler = BlockingQueueHandler(LinkedBlockingDeque()) + return JNILiveliness.get( + jniSession, + keyExpr, + handler::handle, + receiver = handler.receiver(), + timeout, + onClose = handler::onClose + ) + } + + /** + * Query the liveliness tokens with matching key expressions. + * + * @param keyExpr The [KeyExpr] for the query. + * @param callback [Callback] to handle the incoming replies. + * @param timeout Optional timeout of the query, defaults to 10 secs. + */ + @JvmOverloads + @Throws(ZError::class) + fun get( + keyExpr: KeyExpr, callback: Callback, timeout: Duration = Duration.ofMillis(10000) + ) { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.get(jniSession, keyExpr, callback, Unit, timeout, {}) + } + + /** + * Query the liveliness tokens with matching key expressions. + * + * @param R The [Handler.receiver] type. + * @param keyExpr The [KeyExpr] for the query. + * @param handler [Handler] to deal with the incoming replies. + * @param timeout Optional timeout of the query, defaults to 10 secs. + */ + @JvmOverloads + @Throws(ZError::class) + fun get( + keyExpr: KeyExpr, handler: Handler, timeout: Duration = Duration.ofMillis(10000) + ): R { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + val callback = handler::handle + return JNILiveliness.get( + jniSession, + keyExpr, + callback, + handler.receiver(), + timeout, + onClose = handler::onClose + ) + } + + /** + * Create a [Subscriber] for liveliness changes matching the given key expression. + * + * @param keyExpr The [KeyExpr] the subscriber will be listening to. + * @param options Optional [LivelinessSubscriberOptions] parameter for subscriber configuration. + */ + @JvmOverloads + @Throws(ZError::class) + fun declareSubscriber( + keyExpr: KeyExpr, + options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() + ): HandlerSubscriber>> { + val handler = BlockingQueueHandler(LinkedBlockingDeque()) + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.declareSubscriber( + jniSession, + keyExpr, + handler::handle, + handler.receiver(), + options.history, + handler::onClose + ) + } + + /** + * Create a [Subscriber] for liveliness changes matching the given key expression. + * + * @param keyExpr The [KeyExpr] the subscriber will be listening to. + * @param callback The [Callback] to be run when a liveliness change is received. + * @param options Optional [LivelinessSubscriberOptions] parameter for subscriber configuration. + */ + @JvmOverloads + @Throws(ZError::class) + fun declareSubscriber( + keyExpr: KeyExpr, + callback: Callback, + options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() + ): CallbackSubscriber { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.declareSubscriber( + jniSession, + keyExpr, + callback, + options.history, + fun() {} + ) + } + + /** + * Create a [Subscriber] for liveliness changes matching the given key expression. + * + * @param R The [Handler.receiver] type. + * @param keyExpr The [KeyExpr] the subscriber will be listening to. + * @param handler [Handler] to handle liveliness changes events. + * @param options Optional [LivelinessSubscriberOptions] parameter for subscriber configuration. + */ + @JvmOverloads + @Throws(ZError::class) + fun declareSubscriber( + keyExpr: KeyExpr, + handler: Handler, + options: LivelinessSubscriberOptions = LivelinessSubscriberOptions() + ): HandlerSubscriber { + val jniSession = session.jniSession ?: throw Session.sessionClosedException + return JNILiveliness.declareSubscriber( + jniSession, + keyExpr, + handler::handle, + handler.receiver(), + options.history, + handler::onClose + ) + } +} + +/** + * Options for the [Liveliness] subscriber. + */ +data class LivelinessSubscriberOptions(var history: Boolean = false) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt new file mode 100644 index 00000000..5d95b8fa --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/liveliness/LivelinessToken.kt @@ -0,0 +1,53 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.liveliness + +import io.zenoh.jni.JNILivelinessToken +import io.zenoh.session.SessionDeclaration + +/** + * A token whose liveliness is tied to the Zenoh [io.zenoh.Session]. + * + * A declared liveliness token will be seen as alive by any other Zenoh + * application in the system that monitors it while the liveliness token + * is not undeclared or dropped, while the Zenoh application that declared + * it is alive (didn't stop or crashed) and while the Zenoh application + * that declared the token has Zenoh connectivity with the Zenoh application + * that monitors it. + * + * Liveliness tokens are automatically undeclared when dropped. + */ +class LivelinessToken internal constructor(private var jniLivelinessToken: JNILivelinessToken?): SessionDeclaration, AutoCloseable { + + /** + * Undeclares the token. + */ + override fun undeclare() { + jniLivelinessToken?.undeclare() + jniLivelinessToken = null + } + + /** + * Closes the token. This function is equivalent to [undeclare]. + * When using try-with-resources, this function is called automatically. + */ + override fun close() { + undeclare() + } + + protected fun finalize() { + undeclare() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Encoding.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Encoding.kt deleted file mode 100644 index 22e3fdc5..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Encoding.kt +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.prelude - -/** - * Default encoding values used by Zenoh. - * - * An encoding has a similar role to Content-type in HTTP: it indicates, when present, how data should be interpreted by the application. - * - * Please note the Zenoh protocol does not impose any encoding value, nor it operates on it. - * It can be seen as some optional metadata that is carried over by Zenoh in such a way the application may perform different operations depending on the encoding value. - * - * A set of associated constants are provided to cover the most common encodings for user convenience. - * This is particularly useful in helping Zenoh to perform additional network optimizations. - * - */ -class Encoding(val id: ID, val schema: String? = null) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Encoding - - if (id != other.id) return false - return schema == other.schema - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + schema.hashCode() - return result - } - - /** - * The ID of the encoding. - * - * @property id The id of the encoding. - * @property encoding The encoding name. - */ - enum class ID(val id: Int, val encoding: String) { - ZENOH_BYTES(0, "zenoh/bytes"), - ZENOH_INT(1, "zenoh/int"), - ZENOH_UINT(2, "zenoh/uint"), - ZENOH_FLOAT(3, "zenoh/float"), - ZENOH_BOOL(4, "zenoh/bool"), - ZENOH_STRING(5, "zenoh/string"), - ZENOH_ERROR(6, "zenoh/error"), - APPLICATION_OCTET_STREAM(7, "application/octet-stream"), - TEXT_PLAIN(8, "text/plain"), - APPLICATION_JSON(9, "application/json"), - TEXT_JSON(10, "text/json"), - APPLICATION_CDR(11, "application/cdr"), - APPLICATION_CBOR(12, "application/cbor"), - APPLICATION_YAML(13, "application/yaml"), - TEXT_YAML(14, "text/yaml"), - TEXT_JSON5(15, "text/json5"), - APPLICATION_PYTHON_SERIALIZED_OBJECT(16, "application/python-serialized-object"), - APPLICATION_PROTOBUF(17, "application/protobuf"), - APPLICATION_JAVA_SERIALIZED_OBJECT(18, "application/java-serialized-object"), - APPLICATION_OPENMETRICS_TEXT(19, "application/openmetrics-text"), - IMAGE_PNG(20, "image/png"), - IMAGE_JPEG(21, "image/jpeg"), - IMAGE_GIF(22, "image/gif"), - IMAGE_BMP(23, "image/bmp"), - IMAGE_WEBP(24, "image/webp"), - APPLICATION_XML(25, "application/xml"), - APPLICATION_X_WWW_FORM_URLENCODED(26, "application/x-www-form-urlencoded"), - TEXT_HTML(27, "text/html"), - TEXT_XML(28, "text/xml"), - TEXT_CSS(29, "text/css"), - TEXT_JAVASCRIPT(30, "text/javascript"), - TEXT_MARKDOWN(31, "text/markdown"), - TEXT_CSV(32, "text/csv"), - APPLICATION_SQL(33, "application/sql"), - APPLICATION_COAP_PAYLOAD(34, "application/coap-payload"), - APPLICATION_JSON_PATCH_JSON(35, "application/json-patch+json"), - APPLICATION_JSON_SEQ(36, "application/json-seq"), - APPLICATION_JSONPATH(37, "application/jsonpath"), - APPLICATION_JWT(38, "application/jwt"), - APPLICATION_MP4(39, "application/mp4"), - APPLICATION_SOAP_XML(40, "application/soap+xml"), - APPLICATION_YANG(41, "application/yang"), - AUDIO_AAC(42, "audio/aac"), - AUDIO_FLAC(43, "audio/flac"), - AUDIO_MP4(44, "audio/mp4"), - AUDIO_OGG(45, "audio/ogg"), - AUDIO_VORBIS(46, "audio/vorbis"), - VIDEO_H261(47, "video/h261"), - VIDEO_H263(48, "video/h263"), - VIDEO_H264(49, "video/h264"), - VIDEO_H265(50, "video/h265"), - VIDEO_H266(51, "video/h266"), - VIDEO_MP4(52, "video/mp4"), - VIDEO_OGG(53, "video/ogg"), - VIDEO_RAW(54, "video/raw"), - VIDEO_VP8(55, "video/vp8"), - VIDEO_VP9(56, "video/vp9"); - - companion object { - private val idToEnum = entries.associateBy(ID::id) - fun fromId(id: Int): ID? = idToEnum[id] - } - } -} - - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/QoS.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/QoS.kt deleted file mode 100644 index 1984006a..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/QoS.kt +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.prelude - -/** - * Quality of service settings used to send zenoh message. - * - * @property express If true, the message is not batched in order to reduce the latency. - * @property congestionControl [CongestionControl] policy used for the message. - * @property priority [Priority] policy used for the message. - */ -class QoS internal constructor( - internal val express: Boolean, - internal val congestionControl: CongestionControl, - internal val priority: Priority -) { - - internal constructor(express: Boolean, congestionControl: Int, priority: Int) : this( - express, CongestionControl.fromInt(congestionControl), Priority.fromInt(priority) - ) - - /** - * Returns priority of the message. - */ - fun priority(): Priority = priority - - /** - * Returns congestion control setting of the message. - */ - fun congestionControl(): CongestionControl = congestionControl - - /** - * Returns express flag. If it is true, the message is not batched to reduce the latency. - */ - fun isExpress(): Boolean = express - - companion object { - fun default() = QoS(false, CongestionControl.default(), Priority.default()) - } - - internal class Builder( - private var express: Boolean = false, - private var congestionControl: CongestionControl = CongestionControl.default(), - private var priority: Priority = Priority.default(), - ) { - - fun express(value: Boolean) = apply { this.express = value } - - fun priority(priority: Priority) = apply { this.priority = priority } - - fun congestionControl(congestionControl: CongestionControl) = - apply { this.congestionControl = congestionControl } - - fun build() = QoS(express, congestionControl, priority) - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Delete.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Delete.kt deleted file mode 100644 index 22cf7f60..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Delete.kt +++ /dev/null @@ -1,104 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.publication - -import io.zenoh.Resolvable -import io.zenoh.Session -import io.zenoh.exceptions.ZenohException -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Priority -import io.zenoh.prelude.QoS -import kotlin.Throws - -/** - * Delete operation to perform on Zenoh on a key expression. - * - * Example: - * ```java - * public void deleteExample() throws ZenohException { - * System.out.println("Opening session..."); - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/example")) { - * session.delete(keyExpr).res(); - * System.out.println("Performed a delete on '" + keyExpr); - * } - * } - * } - * ``` - * - * A delete operation is a special case of a Put operation, it is analogous to perform a Put with an empty value and - * specifying the sample kind to be `DELETE`. - */ -class Delete private constructor( - val keyExpr: KeyExpr, val qos: QoS, val attachment: ByteArray? -) { - - companion object { - /** - * Creates a new [Builder] associated with the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the Delete will be performed. - * @param keyExpr The [KeyExpr] upon which the Delete will be performed. - * @return A [Delete] operation [Builder]. - */ - fun newBuilder(session: Session, keyExpr: KeyExpr): Builder { - return Builder(session, keyExpr) - } - } - - /** - * Builder to construct a [Delete] operation. - * - * @property session The [Session] from which the Delete will be performed - * @property keyExpr The [KeyExpr] from which the Delete will be performed - * @constructor Create a [Delete] builder. - */ - class Builder internal constructor( - val session: Session, - val keyExpr: KeyExpr, - ) : Resolvable { - - private var qosBuilder: QoS.Builder = QoS.Builder() - private var attachment: ByteArray? = null - - /** Change the [CongestionControl] to apply when routing the data. */ - fun congestionControl(congestionControl: CongestionControl) = - apply { this.qosBuilder.congestionControl(congestionControl) } - - /** Change the [Priority] of the written data. */ - fun priority(priority: Priority) = apply { this.qosBuilder.priority(priority) } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(isExpress: Boolean) = apply { this.qosBuilder.express(isExpress) } - - /** Set an attachment to the put operation. */ - fun withAttachment(attachment: ByteArray) = apply { this.attachment = attachment } - - /** - * Performs a DELETE operation on the specified [keyExpr]. - * - * A successful [Result] only states the Delete request was properly sent through the network, it doesn't mean it - * was properly executed remotely. - */ - @Throws(ZenohException::class) - override fun res() { - val delete = Delete(this.keyExpr, qosBuilder.build(), attachment) - session.resolveDelete(keyExpr, delete) - } - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Publisher.kt deleted file mode 100644 index e22298bc..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Publisher.kt +++ /dev/null @@ -1,170 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.publication - -import io.zenoh.* -import io.zenoh.exceptions.SessionException -import io.zenoh.exceptions.ZenohException -import io.zenoh.jni.JNIPublisher -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Priority -import io.zenoh.prelude.QoS -import io.zenoh.value.Value -import kotlin.Throws - -/** - * A Zenoh Publisher. - * - * A publisher is automatically dropped when using it with the 'try-with-resources' statement (i.e. 'use' in Kotlin). - * The session from which it was declared will also keep a reference to it and undeclare it once the session is closed. - * - * In order to declare a publisher, [Session.declarePublisher] must be called, which returns a [Publisher.Builder] from - * which we can specify the [Priority], and the [CongestionControl]. - * - * Example: - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/greeting")) { - * System.out.println("Declaring publisher on '" + keyExpr + "'..."); - * try (Publisher publisher = session.declarePublisher(keyExpr).res()) { - * int i = 0; - * while (true) { - * publisher.put("Hello for the " + i + "th time!").res(); - * Thread.sleep(1000); - * i++; - * } - * } - * } - * } catch (ZenohException | InterruptedException e) { - * System.out.println("Error: " + e); - * } - * ``` - * - * The publisher configuration parameters can be later changed using the setter functions. - * - * @property keyExpr The key expression the publisher will be associated to. - * @property qos [QoS] configuration of the publisher. - * @property jniPublisher Delegate class handling the communication with the native code. - * @constructor Create empty Publisher with the default configuration. - */ -class Publisher internal constructor( - val keyExpr: KeyExpr, - private var qos: QoS, - private var jniPublisher: JNIPublisher?, -) : SessionDeclaration, AutoCloseable { - - companion object { - private val sessionException = SessionException("Publisher is not valid.") - } - - /** Performs a PUT operation on the specified [keyExpr] with the specified [value]. */ - fun put(value: Value) = Put(jniPublisher, value) - - /** Performs a PUT operation on the specified [keyExpr] with the specified string [value]. */ - fun put(value: String) = Put(jniPublisher, Value(value)) - - /** - * Performs a DELETE operation on the specified [keyExpr] - * - * @return A [Resolvable] operation. - */ - fun delete() = Delete(jniPublisher) - - /** Get congestion control policy. */ - fun getCongestionControl(): CongestionControl { - return qos.congestionControl() - } - - /** Get priority policy. */ - fun getPriority(): Priority { - return qos.priority() - } - - override fun isValid(): Boolean { - return jniPublisher != null - } - - override fun close() { - undeclare() - } - - override fun undeclare() { - jniPublisher?.close() - jniPublisher = null - } - - @Suppress("removal") - protected fun finalize() { - jniPublisher?.close() - } - - class Put internal constructor( - private var jniPublisher: JNIPublisher?, - val value: Value, - var attachment: ByteArray? = null - ) : Resolvable { - - fun withAttachment(attachment: ByteArray) = apply { this.attachment = attachment } - - override fun res() { - jniPublisher?.put(value, attachment) ?: throw(sessionException) - } - } - - class Delete internal constructor( - private var jniPublisher: JNIPublisher?, - var attachment: ByteArray? = null - ) : Resolvable { - - fun withAttachment(attachment: ByteArray) = apply { this.attachment = attachment } - - @Throws(ZenohException::class) - override fun res() { - jniPublisher?.delete(attachment) ?: throw(sessionException) - } - } - - /** - * Publisher Builder. - * - * @property session The [Session] from which the publisher is declared. - * @property keyExpr The key expression the publisher will be associated to. - * @constructor Create empty Builder. - */ - class Builder internal constructor( - internal val session: Session, - internal val keyExpr: KeyExpr, - ) { - private var qosBuilder: QoS.Builder = QoS.Builder() - - /** Change the [CongestionControl] to apply when routing the data. */ - fun congestionControl(congestionControl: CongestionControl) = - apply { this.qosBuilder.congestionControl(congestionControl) } - - /** Change the [Priority] of the written data. */ - fun priority(priority: Priority) = apply { this.qosBuilder.priority(priority) } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(isExpress: Boolean) = apply { this.qosBuilder.express(isExpress) } - - fun res(): Publisher { - return session.run { resolvePublisher(keyExpr, qosBuilder.build()) } - } - } -} - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Put.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Put.kt deleted file mode 100644 index 94266a25..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/publication/Put.kt +++ /dev/null @@ -1,117 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.publication - -import io.zenoh.Resolvable -import io.zenoh.Session -import io.zenoh.exceptions.ZenohException -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.* -import io.zenoh.value.Value - -/** - * Put operation. - * - * A put puts a [io.zenoh.sample.Sample] into the specified key expression. - * - * Example: - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-put")) { - * String value = "Put from Java!"; - * session.put(keyExpr, value) - * .congestionControl(CongestionControl.BLOCK) - * .priority(Priority.REALTIME) - * .kind(SampleKind.PUT) - * .res(); - * System.out.println("Putting Data ('" + keyExpr + "': '" + value + "')..."); - * } - * } - * ``` - * - * This class is an open class for the sake of the [Delete] operation, which is a special case of [Put] operation. - * - * @property keyExpr The [KeyExpr] to which the put operation will be performed. - * @property value The [Value] to put. - * @property qos The [QoS] configuration. - * @property attachment An optional user attachment. - */ -class Put private constructor( - val keyExpr: KeyExpr, - val value: Value, - val qos: QoS, - val attachment: ByteArray? -) { - - companion object { - - /** - * Creates a bew [Builder] associated to the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the put will be performed. - * @param keyExpr The [KeyExpr] upon which the put will be performed. - * @param value The [Value] to put. - * @return A [Put] operation [Builder]. - */ - internal fun newBuilder(session: Session, keyExpr: KeyExpr, value: Value): Builder { - return Builder(session, keyExpr, value) - } - } - - /** - * Builder to construct a [Put] operation. - * - * @property session The [Session] from which the put operation will be performed. - * @property keyExpr The [KeyExpr] upon which the put operation will be performed. - * @property value The [Value] to put. - * @constructor Create a [Put] builder. - */ - class Builder internal constructor( - private val session: Session, - private val keyExpr: KeyExpr, - private var value: Value, - ): Resolvable { - - private var qosBuilder: QoS.Builder = QoS.Builder() - private var attachment: ByteArray? = null - - /** Change the [Encoding] of the written data. */ - fun encoding(encoding: Encoding) = apply { - this.value = Value(value.payload, encoding) - } - - /** Change the [CongestionControl] to apply when routing the data. */ - fun congestionControl(congestionControl: CongestionControl) = - apply { this.qosBuilder.congestionControl(congestionControl) } - - /** Change the [Priority] of the written data. */ - fun priority(priority: Priority) = apply { this.qosBuilder.priority(priority) } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(isExpress: Boolean) = apply { this.qosBuilder.express(isExpress) } - - /** Set an attachment to the put operation. */ - fun withAttachment(attachment: ByteArray) = apply { this.attachment = attachment } - - /** Resolves the put operation, returning a [Result]. */ - @Throws(ZenohException::class) - override fun res() { - val put = Put(keyExpr, value, qosBuilder.build(), attachment) - session.run { resolvePut(keyExpr, put) } - } - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/DeleteOptions.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/DeleteOptions.kt new file mode 100644 index 00000000..50980816 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/DeleteOptions.kt @@ -0,0 +1,38 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.pubsub + +import io.zenoh.bytes.IntoZBytes +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.qos.QoS +import io.zenoh.qos.Reliability + +/** + * Options for delete operations. + * + * @param reliability The [Reliability] desired. + * @param attachment Optional attachment for the delete operation. + * @property express [QoS] express value. + * @property congestionControl The congestion control policy. + * @property priority The priority policy. + */ +data class DeleteOptions( + var reliability: Reliability = Reliability.RELIABLE, + var attachment: IntoZBytes? = null, + var express: Boolean = QoS.defaultQoS.express, + var congestionControl: CongestionControl = QoS.defaultQoS.congestionControl, + var priority: Priority = QoS.defaultQoS.priority +) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt new file mode 100644 index 00000000..07521583 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Publisher.kt @@ -0,0 +1,120 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.pubsub + +import io.zenoh.* +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes +import io.zenoh.exceptions.ZError +import io.zenoh.jni.JNIPublisher +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.session.SessionDeclaration +import kotlin.Throws + +/** + * A Zenoh Publisher. + * + * A publisher is automatically dropped when using it with the 'try-with-resources' statement (i.e. 'use' in Kotlin). + * The session from which it was declared will also keep a reference to it and undeclare it once the session is closed. + * + * In order to declare a publisher, [Session.declarePublisher] must be called. + * + * Example: + * ```java + * try (Session session = Session.open()) { + * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/greeting")) { + * System.out.println("Declaring publisher on '" + keyExpr + "'..."); + * try (Publisher publisher = session.declarePublisher(keyExpr)) { + * int i = 0; + * while (true) { + * var payload = ZBytes.from("Hello for the " + i + "th time!"); + * publisher.put(payload); + * Thread.sleep(1000); + * i++; + * } + * } + * } + * } catch (ZError | InterruptedException e) { + * System.out.println("Error: " + e); + * } + * ``` + * + * The publisher configuration parameters can be later changed using the setter functions. + * + * @property keyExpr The key expression the publisher will be associated to. + * @property encoding The encoding user by the publisher. + */ +class Publisher internal constructor( + val keyExpr: KeyExpr, + private var congestionControl: CongestionControl, + private var priority: Priority, + val encoding: Encoding, + private var jniPublisher: JNIPublisher?, +) : SessionDeclaration, AutoCloseable { + + companion object { + private val publisherNotValid = ZError("Publisher is not valid.") + } + + /** Get the congestion control applied when routing the data. */ + fun congestionControl() = congestionControl + + /** Get the priority of the written data. */ + fun priority() = priority + + /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ + @Throws(ZError::class) + fun put(payload: IntoZBytes) { + jniPublisher?.put(payload, encoding, null) ?: throw publisherNotValid + } + + /** Performs a PUT operation on the specified [keyExpr] with the specified [payload]. */ + @Throws(ZError::class) + fun put(payload: IntoZBytes, options: PutOptions) { + jniPublisher?.put(payload, options.encoding ?: this.encoding, options.attachment) ?: throw publisherNotValid + } + + /** + * Performs a DELETE operation on the specified [keyExpr] + */ + @JvmOverloads + @Throws(ZError::class) + fun delete(options: DeleteOptions = DeleteOptions()) { + jniPublisher?.delete(options.attachment) ?: throw(publisherNotValid) + } + + /** + * Returns `true` if the publisher is still running. + */ + fun isValid(): Boolean { + return jniPublisher != null + } + + override fun close() { + undeclare() + } + + override fun undeclare() { + jniPublisher?.close() + jniPublisher = null + } + + @Suppress("removal") + protected fun finalize() { + jniPublisher?.close() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PublisherOptions.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PublisherOptions.kt new file mode 100644 index 00000000..0759a5b4 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PublisherOptions.kt @@ -0,0 +1,33 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.pubsub + +import io.zenoh.bytes.Encoding +import io.zenoh.qos.* + +/** + * Options for the publisher. + * + * @param reliability The desired reliability. + * @param encoding The encoding of the payload. + * @property express [QoS] express value. + * @property congestionControl The congestion control policy. + * @property priority The priority policy. + */ +data class PublisherOptions(var reliability: Reliability = Reliability.RELIABLE, + var encoding: Encoding = Encoding.defaultEncoding(), + var express: Boolean = QoS.defaultQoS.express, + var congestionControl: CongestionControl = QoS.defaultQoS.congestionControl, + var priority: Priority = QoS.defaultQoS.priority) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PutOptions.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PutOptions.kt new file mode 100644 index 00000000..e5930d61 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/PutOptions.kt @@ -0,0 +1,38 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.pubsub + +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes +import io.zenoh.qos.* + +/** + * Options for the PUT operations. + * + * @param encoding The encoding of the payload. + * @param reliability The desired reliability. + * @param attachment Optional attachment. + * @property express [QoS] express value. + * @property congestionControl The congestion control policy. + * @property priority The priority policy. + */ +data class PutOptions( + var encoding: Encoding? = null, + var reliability: Reliability = Reliability.RELIABLE, + var attachment: IntoZBytes? = null, + var express: Boolean = QoS.defaultQoS.express, + var congestionControl: CongestionControl = QoS.defaultQoS.congestionControl, + var priority: Priority = QoS.defaultQoS.priority +) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt new file mode 100644 index 00000000..a3688303 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/pubsub/Subscriber.kt @@ -0,0 +1,122 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.pubsub + +import io.zenoh.handlers.BlockingQueueHandler +import io.zenoh.jni.JNISubscriber +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.session.SessionDeclaration + +/** + * A subscriber that allows listening to updates on a key expression and reacting to changes. + * + * Its main purpose is to keep the subscription active as long as it exists. + * + * Example using the default [BlockingQueueHandler] handler: + * + * ```java + * var queue = session.declareSubscriber("a/b/c"); + * try (Session session = Zenoh.open(config)) { + * try (var subscriber = session.declareSubscriber(keyExpr)) { + * var receiver = subscriber.getReceiver(); + * assert receiver != null; + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * System.out.println(wrapper.get()); + * } + * } + * } + * ``` + * + * Example using a callback: + * ```java + * try (Session session = Zenoh.open(config)) { + * session.declareSubscriber(keyExpr, System.out::println); + * } + * ``` + * + * Example using a handler: + * ```java + * class MyHandler implements Handler> {...} + * + * //... + * try (Session session = Zenoh.open(config)) { + * var handler = new MyHandler(); + * var arraylist = session.declareSubscriber(keyExpr, handler); + * // ... + * } + * ``` + */ +sealed class Subscriber( + val keyExpr: KeyExpr, private var jniSubscriber: JNISubscriber? +) : AutoCloseable, SessionDeclaration { + + fun isValid(): Boolean { + return jniSubscriber != null + } + + override fun undeclare() { + jniSubscriber?.close() + jniSubscriber = null + } + + override fun close() { + undeclare() + } + + protected fun finalize() { + jniSubscriber?.close() + } +} + +/** + * Subscriber using a callback to handle incoming samples. + * + * Example: + * ```java + * try (Session session = Zenoh.open(config)) { + * session.declareSubscriber(keyExpr, System.out::println); + * } + * ``` + */ +class CallbackSubscriber internal constructor(keyExpr: KeyExpr, jniSubscriber: JNISubscriber?): Subscriber(keyExpr, jniSubscriber) + +/** + * Subscriber using a [io.zenoh.handlers.Handler] for handling incoming samples. + * + * Example using the default handler: + * ```java + * try (Session session = Zenoh.open(config)) { + * try (HandlerSubscriber>> subscriber = session.declareSubscriber(keyExpr)) { + * BlockingQueue> receiver = subscriber.getReceiver(); + * assert receiver != null; + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * System.out.println(wrapper.get()); + * } + * } + * } + * ``` + * + * @param R The type of the receiver. + * @param receiver The receiver of the subscriber's handler. + */ +class HandlerSubscriber internal constructor(keyExpr: KeyExpr, jniSubscriber: JNISubscriber?, val receiver: R): Subscriber(keyExpr, jniSubscriber) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/CongestionControl.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt similarity index 94% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/CongestionControl.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt index 64d981c1..82b3463a 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/CongestionControl.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/CongestionControl.kt @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -package io.zenoh.prelude +package io.zenoh.qos /** The congestion control to be applied when routing the data. */ enum class CongestionControl (val value: Int) { @@ -30,7 +30,5 @@ enum class CongestionControl (val value: Int) { companion object { fun fromInt(value: Int) = entries.first { it.value == value } - - fun default() = DROP } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Priority.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt similarity index 94% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Priority.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt index 6907565d..0e27780e 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/Priority.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Priority.kt @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -package io.zenoh.prelude +package io.zenoh.qos /** * The Priority of Zenoh messages. @@ -33,8 +33,5 @@ enum class Priority(val value: Int) { companion object { fun fromInt(value: Int) = entries.first { it.value == value } - - fun default() = DATA } } - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/QoS.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/QoS.kt new file mode 100644 index 00000000..7b18eedd --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/QoS.kt @@ -0,0 +1,33 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.qos + +/** + * Quality of service settings used to send zenoh message. + * + * @property congestionControl [CongestionControl] policy used for the message. + * @property priority [Priority] policy used for the message. + * @property express If true, the message is not batched in order to reduce the latency. + */ +data class QoS ( + var congestionControl: CongestionControl = CongestionControl.DROP, + var priority: Priority = Priority.DATA, + var express: Boolean = false +) { + + companion object { + internal val defaultQoS = QoS() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Reliability.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt similarity index 97% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Reliability.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt index d675758e..d574f27c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Reliability.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/qos/Reliability.kt @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -package io.zenoh.subscriber +package io.zenoh.qos /** * The reliability policy. diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt index a95fa0d3..f4c1b29c 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/ConsolidationMode.kt @@ -35,8 +35,4 @@ enum class ConsolidationMode { /** Holds back samples to only send the set of samples that had the highest timestamp for their key. */ LATEST; - - companion object { - fun default() = AUTO - } } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Get.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Get.kt index fcd74e9e..08d7a506 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Get.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Get.kt @@ -14,181 +14,25 @@ package io.zenoh.query -import io.zenoh.handlers.Callback -import io.zenoh.Session -import io.zenoh.exceptions.ZenohException -import io.zenoh.handlers.BlockingQueueHandler -import io.zenoh.handlers.Handler -import io.zenoh.selector.Selector -import io.zenoh.value.Value +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes import java.time.Duration -import java.util.* -import java.util.concurrent.BlockingQueue -import java.util.concurrent.LinkedBlockingDeque /** * Get to query data from the matching queryables in the system. * - * Example with a [Callback]: - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/java/example")) { - * session.get(keyExpr) - * .consolidation(ConsolidationMode.NONE) - * .withValue("Get value example") - * .with(reply -> System.out.println("Received reply " + reply)) - * .res() - * } - * } - * ``` - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, R will be [Unit]. + * @param timeout Timeout of the query. + * @param target The [QueryTarget] of the query. + * @param consolidation The [ConsolidationMode] of the query. + * @param payload Optional payload. + * @param encoding Encoding of the payload. + * @param attachment Optional attachment. */ -class Get private constructor() { - - companion object { - /** - * Creates a bew [Builder] associated to the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the query will be triggered. - * @param selector The [Selector] with which the query will be performed. - * @return A [Builder] with a default [BlockingQueueHandler] to handle any incoming [Reply]. - */ - fun newBuilder(session: Session, selector: Selector): Builder>> { - return Builder(session, selector, handler = BlockingQueueHandler(LinkedBlockingDeque())) - } - } - - /** - * Builder to construct a [Get]. - * - * Either a [Handler] or a [Callback] must be specified. Note neither of them are stackable and are mutually exclusive, - * meaning that it is not possible to specify multiple callbacks and/or handlers, the builder only considers the - * last one specified. - * - * @param R The receiver type of the [Handler] implementation, defaults to [Unit] when no handler is specified. - * @property session The [Session] from which the query will be performed. - * @property selector The [Selector] with which the get query will be performed. - * @constructor Creates a Builder. This constructor is internal and should not be called directly. Instead, this - * builder should be obtained through the [Session] after calling [Session.get]. - */ - class Builder internal constructor( - private val session: Session, - private val selector: Selector, - private var callback: Callback? = null, - private var handler: Handler? = null, - ) { - - private var timeout = Duration.ofMillis(10000) - private var target: QueryTarget = QueryTarget.BEST_MATCHING - private var consolidation: ConsolidationMode = ConsolidationMode.default() - private var value: Value? = null - private var attachment: ByteArray? = null - private var onClose: (() -> Unit)? = null - - private constructor(other: Builder<*>, handler: Handler?) : this(other.session, other.selector) { - this.handler = handler - copyParams(other) - } - - private constructor(other: Builder<*>, callback: Callback?) : this(other.session, other.selector) { - this.callback = callback - copyParams(other) - } - - private fun copyParams(other: Builder<*>) { - this.timeout = other.timeout - this.target = other.target - this.consolidation = other.consolidation - this.value = other.value - this.attachment = other.attachment - this.onClose = other.onClose - } - - /** Specify the [QueryTarget]. */ - fun target(target: QueryTarget): Builder { - this.target = target - return this - } - - /** Specify the [ConsolidationMode]. */ - fun consolidation(consolidation: ConsolidationMode): Builder { - this.consolidation = consolidation - return this - } - - /** Specify the timeout. */ - fun timeout(timeout: Duration): Builder { - this.timeout = timeout - return this - } - - /** - * Specify a string value. A [Value] is generated with the provided message, therefore - * this method is equivalent to calling `withValue(Value(message))`. - */ - fun withValue(message: String): Builder { - this.value = Value(message) - return this - } - - /** Specify a [Value]. */ - fun withValue(value: Value): Builder { - this.value = value - return this - } - - /** Specify an attachment. */ - fun withAttachment(attachment: ByteArray): Builder { - this.attachment = attachment - return this - } - - /** - * Specify an action to be invoked when the Get operation is over. - * - * Zenoh will trigger ths specified action once no more replies are to be expected. - */ - fun onClose(action: () -> Unit): Builder { - this.onClose = action - return this - } - - /** Specify a [Callback]. Overrides any previously specified callback or handler. */ - fun with(callback: Callback): Builder = Builder(this, callback) - - /** Specify a [Handler]. Overrides any previously specified callback or handler. */ - fun with(handler: Handler): Builder = Builder(this, handler) - - /** Specify a [BlockingQueue]. Overrides any previously specified callback or handler. */ - fun with(blockingQueue: BlockingQueue>): Builder>> = Builder(this, BlockingQueueHandler(blockingQueue)) - - /** - * Resolve the builder triggering the query. - * - * @return The receiver [R] from the specified [Handler] (if specified). - */ - @Throws(ZenohException::class) - fun res(): R? { - require(callback != null || handler != null) { "Either a callback or a handler must be provided." } - val resolvedCallback = callback ?: Callback { t: Reply -> handler?.handle(t) } - val resolvedOnClose = fun() { - onClose?.invoke() - handler?.onClose() - } - return session.run { - resolveGet( - selector, - resolvedCallback, - resolvedOnClose, - handler?.receiver(), - timeout, - target, - consolidation, - value, - attachment - ) - } - } - } -} +data class GetOptions( + var timeout: Duration = Duration.ofMillis(10000), + var target: QueryTarget = QueryTarget.BEST_MATCHING, + var consolidation: ConsolidationMode = ConsolidationMode.AUTO, + var payload: IntoZBytes? = null, + var encoding: Encoding? = null, + var attachment: IntoZBytes? = null +) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt new file mode 100644 index 00000000..241ade35 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Parameters.kt @@ -0,0 +1,148 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.query + +import java.net.URLDecoder + +/** + * Parameters of the [Selector]. + * + * When in string form, the `parameters` should be encoded like the query section of a URL: + * - parameters are separated by `;`, + * - the parameter name and value are separated by the first `=`, + * - in the absence of `=`, the parameter value is considered to be the empty string, + * - both name and value should use percent-encoding to escape characters, + * - defining a value for the same parameter name twice is considered undefined behavior and an + * error result is returned. + * + * @see Selector + */ +data class Parameters internal constructor(private val params: MutableMap) : IntoParameters { + + companion object { + + private const val LIST_SEPARATOR = ";" + private const val FIELD_SEPARATOR = "=" + private const val VALUE_SEPARATOR = "|" + + /** + * Creates an empty Parameters. + */ + @JvmStatic + fun empty() = Parameters(mutableMapOf()) + + /** + * Creates a [Parameters] instance from the provided map. + */ + @JvmStatic + fun from(params: Map): Parameters = Parameters(params.toMutableMap()) + + /** + * Attempts to create a [Parameters] from a string. + * + * When in string form, the `parameters` should be encoded like the query section of a URL: + * - parameters are separated by `;`, + * - the parameter name and value are separated by the first `=`, + * - in the absence of `=`, the parameter value is considered to be the empty string, + * - both name and value should use percent-encoding to escape characters, + * - defining a value for the same parameter name twice is considered undefined behavior and an + * error result is returned. + */ + @JvmStatic + fun from(params: String): Parameters { + if (params.isBlank()) { + return Parameters(mutableMapOf()) + } + return params.split(LIST_SEPARATOR).fold(mutableMapOf()) { parametersMap, parameter -> + val (key, value) = parameter.split(FIELD_SEPARATOR).let { it[0] to it.getOrNull(1) } + require(!parametersMap.containsKey(key)) { "Duplicated parameter `$key` detected." } + parametersMap[key] = value?.let { URLDecoder.decode(it, Charsets.UTF_8.name()) } ?: "" + parametersMap + }.let { Parameters(it) } + } + } + + override fun toString(): String = + params.entries.joinToString(LIST_SEPARATOR) { "${it.key}$FIELD_SEPARATOR${it.value}" } + + override fun into(): Parameters = this + + /** + * Returns empty if no parameters were provided. + */ + fun isEmpty(): Boolean = params.isEmpty() + + /** + * Returns true if the [key] is contained. + */ + fun containsKey(key: String): Boolean = params.containsKey(key) + + /** + * Returns the value of the [key] if present. + */ + fun get(key: String): String? = params[key] + + /** + * Returns the value of the [key] if present, or if not, the [default] value provided. + */ + fun getOrDefault(key: String, default: String): String = params.getOrDefault(key, default) + + /** + * Returns the values of the [key] if present. + * + * Example: + * ```java + * var parameters = Parameters.from("a=1;b=2;c=1|2|3"); + * assertEquals(List.of("1", "2", "3"), parameters.values("c")) + * ``` + */ + fun values(key: String): List? = params[key]?.split(VALUE_SEPARATOR) + + /** + * Inserts the key-value pair into the parameters, returning the old value + * in case of it being already present. + */ + fun insert(key: String, value: String): String? = params.put(key, value) + + /** + * Removes the [key] parameter, returning its value. + */ + fun remove(key: String): String? = params.remove(key) + + /** + * Extends the parameters with the [parameters] provided, overwriting + * any conflicting params. + */ + fun extend(parameters: IntoParameters) { + params.putAll(parameters.into().params) + } + + /** + * Extends the parameters with the [parameters] provided, overwriting + * any conflicting params. + */ + fun extend(parameters: Map) { + params.putAll(parameters) + } + + /** + * Returns a map with the key value pairs of the parameters. + */ + fun toMap(): Map = params +} + +interface IntoParameters { + fun into(): Parameters +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt new file mode 100644 index 00000000..7fb349d4 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Query.kt @@ -0,0 +1,123 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.query + +import io.zenoh.ZenohType +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.IntoZBytes +import io.zenoh.bytes.ZBytes +import io.zenoh.exceptions.ZError +import io.zenoh.jni.JNIQuery +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.qos.QoS +import io.zenoh.sample.Sample +import io.zenoh.sample.SampleKind + +/** + * Represents a Zenoh Query in Kotlin. + * + * A Query is generated within the context of a [Queryable], when receiving a [Query] request. + * + * @property keyExpr The key expression to which the query is associated. + * @property selector The selector + * @property payload Optional payload in case the received query was declared using "with query". + * @property encoding Encoding of the [payload]. + * @property attachment Optional attachment. + */ +class Query internal constructor( + val keyExpr: KeyExpr, + val selector: Selector, + val payload: ZBytes?, + val encoding: Encoding?, + val attachment: ZBytes?, + private var jniQuery: JNIQuery? +) : AutoCloseable, ZenohType { + + /** Shortcut to the [selector]'s parameters. */ + val parameters = selector.parameters + + /** + * Reply to the specified key expression. + * + * @param keyExpr Key expression to reply to. This parameter must not be necessarily the same + * as the key expression from the Query, however it must intersect with the query key. + * @param options Optional options for configuring the reply. + */ + @Throws(ZError::class) + @JvmOverloads + fun reply(keyExpr: KeyExpr, payload: IntoZBytes, options: ReplyOptions = ReplyOptions()) { + val sample = Sample( + keyExpr, + payload.into(), + options.encoding, + SampleKind.PUT, + options.timeStamp, + QoS(options.congestionControl, options.priority, options.express), + options.attachment + ) + jniQuery?.apply { + replySuccess(sample) + jniQuery = null + } ?: throw (ZError("Query is invalid")) + } + + /** + * Reply "delete" to the specified key expression. + * + * @param keyExpr Key expression to reply to. This parameter must not be necessarily the same + * as the key expression from the Query, however it must intersect with the query key. + * @param options Optional options for configuring the reply. + */ + @JvmOverloads + @Throws(ZError::class) + fun replyDel(keyExpr: KeyExpr, options: ReplyDelOptions = ReplyDelOptions()) { + jniQuery?.apply { + replyDelete( + keyExpr, + options.timeStamp, + options.attachment, + QoS(options.congestionControl, options.priority, options.express), + ) + jniQuery = null + } ?: throw (ZError("Query is invalid")) + } + + /** + * Reply "error" to the specified key expression. + * + * @param message The error message. + * @param options Optional options for configuring the reply. + */ + @JvmOverloads + @Throws(ZError::class) + fun replyErr(message: IntoZBytes, options: ReplyErrOptions = ReplyErrOptions()) { + jniQuery?.apply { + replyError(message.into(), options.encoding) + jniQuery = null + } ?: throw (ZError("Query is invalid")) + } + + override fun close() { + jniQuery?.apply { + this.close() + jniQuery = null + } + } + + @Suppress("removal") + protected fun finalize() { + close() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt new file mode 100644 index 00000000..eab11c56 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Queryable.kt @@ -0,0 +1,127 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.query + +import io.zenoh.handlers.BlockingQueueHandler +import io.zenoh.handlers.Handler +import io.zenoh.jni.JNIQueryable +import io.zenoh.keyexpr.KeyExpr +import io.zenoh.session.SessionDeclaration + +/** + * A queryable that allows to perform multiple queries on the specified [KeyExpr]. + * + * Its main purpose is to keep the queryable active as long as it exists. + * + * Example using the default [BlockingQueueHandler] handler: + * ```java + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr); + * BlockingQueue> receiver = queryable.getReceiver(); + * assert receiver != null; + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * Query query = wrapper.get(); + * query.reply(query.getKeyExpr(), ZBytes.from("Example reply"); + * } + * } + * ``` + * + * Example using a [io.zenoh.handlers.Callback]: + * ```java + * try (Session session = Zenoh.open(config)) { + * var queryable = session.declareQueryable(keyExpr, query -> query.reply(query.getKeyExpr(), ZBytes.from("Example reply")); + * } + * ``` + * + * @property keyExpr The [KeyExpr] to which the subscriber is associated. + * @property jniQueryable Delegate object in charge of communicating with the underlying native code. + * @see CallbackQueryable + * @see HandlerQueryable + */ +sealed class Queryable( + val keyExpr: KeyExpr, private var jniQueryable: JNIQueryable? +) : AutoCloseable, SessionDeclaration { + + fun isValid(): Boolean { + return jniQueryable != null + } + + /** + * Undeclares the queryable. + */ + override fun undeclare() { + jniQueryable?.close() + jniQueryable = null + } + + /** + * Closes the queryable, equivalent to [undeclare]. This function is automatically called + * when using try with resources. + */ + override fun close() { + undeclare() + } + + protected fun finalize() { + jniQueryable?.close() + } +} + +/** + * [Queryable] receiving replies through a callback. + * + * Example + * ```java + * try (Session session = Zenoh.open(config)) { + * CallbackQueryable queryable = session.declareQueryable(keyExpr, query -> query.reply(query.getKeyExpr(), ZBytes.from("Example reply")); + * } + * ``` + */ +class CallbackQueryable internal constructor(keyExpr: KeyExpr, jniQueryable: JNIQueryable?): Queryable(keyExpr, jniQueryable) + +/** + * [Queryable] receiving replies through a [Handler]. + * + * Example using the default receiver: + * ```java + * try (Session session = Zenoh.open(config)) { + * Queryable>> queryable = session.declareQueryable(keyExpr); + * BlockingQueue> receiver = queryable.getReceiver(); + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * Query query = wrapper.get(); + * query.reply(query.getKeyExpr(), ZBytes.from("Example reply"); + * } + * } + * ``` + * + * @param R The type of the handler's receiver. + * @param receiver The receiver of the queryable's handler. + */ +class HandlerQueryable internal constructor(keyExpr: KeyExpr, jniQueryable: JNIQueryable?, val receiver: R): Queryable(keyExpr, jniQueryable) + +/** + * Options for configuring a [Queryable]. + * + * @param complete The completeness of the information the queryable provides. + */ +data class QueryableOptions(var complete: Boolean = false) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Reply.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Reply.kt index 01d3ed98..15b33e47 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Reply.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Reply.kt @@ -14,161 +14,29 @@ package io.zenoh.query -import io.zenoh.Resolvable import io.zenoh.ZenohType -import io.zenoh.exceptions.ZenohException +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.ZBytes +import io.zenoh.config.ZenohId import io.zenoh.sample.Sample -import io.zenoh.prelude.SampleKind -import io.zenoh.value.Value -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Priority -import io.zenoh.prelude.QoS -import io.zenoh.protocol.ZenohID -import io.zenoh.queryable.Query +import io.zenoh.qos.CongestionControl +import io.zenoh.qos.Priority +import io.zenoh.qos.QoS import org.apache.commons.net.ntp.TimeStamp /** - * Class to represent a Zenoh Reply to a [Get] operation and to a remote [Query]. - * - * A reply can be either successful ([Success]) or an error ([Error]), both having different information. For instance, - * the successful reply will contain a [Sample] while the error reply will only contain a [Value] with the error information. - * - * Replies can either be automatically created when receiving a remote reply after performing a [Get] (in which case the - * [replierId] shows the id of the replier) or created through the builders while answering to a remote [Query] (in that - * case the replier ID is automatically added by Zenoh). - * - * Generating a reply only makes sense within the context of a [Query], therefore builders below are meant to only - * be accessible from [Query.reply]. - * - * Example: - * ```java - * session.declareQueryable(keyExpr).with { query -> - * query.reply(keyExpr) - * .success(Value("Hello")) - * .timestamp(TimeStamp(Date.from(Instant.now()))) - * .res() - * }.res() - * ... - * ``` + * Class to represent a Zenoh Reply to a remote query. * * @property replierId: unique ID identifying the replier. + * @see Success + * @see Error */ -sealed class Reply private constructor(val replierId: ZenohID?) : ZenohType { +sealed class Reply private constructor(val replierId: ZenohId?) : ZenohType { /** - * Builder to construct a [Reply]. - * - * This builder allows you to construct [Success] and [Error] replies. - * - * @property query The received [Query] to reply to. - * @property keyExpr The [KeyExpr] from the queryable, which is at least an intersection of the query's key expression. - * @constructor Create empty Builder + * A Success reply. */ - class Builder internal constructor(val query: Query, val keyExpr: KeyExpr) { - - /** - * Returns a [Success.Builder] with the provided [value]. - * - * @param value The [Value] of the reply. - */ - fun success(value: Value) = Success.Builder(query, keyExpr, value) - - /** - * Returns a [Success.Builder] with a [Value] containing the provided [message]. - * - * It is equivalent to calling `success(Value(message))`. - * - * @param message A string message for the reply. - */ - fun success(message: String) = success(Value(message)) - - /** - * Returns an [Error.Builder] with the provided [value]. - * - * @param value The [Value] of the error reply. - */ - fun error(value: Value) = Error.Builder(query, value) - - /** - * Returns an [Error.Builder] with a [Value] containing the provided [message]. - * - * It is equivalent to calling `error(Value(message))`. - * - * @param message A string message for the error reply. - */ - fun error(message: String) = error(Value(message)) - - /** - * Returns a [Delete.Builder]. - */ - fun delete() = Delete.Builder(query, keyExpr) - - } - - /** - * A successful [Reply]. - * - * @property sample The [Sample] of the reply. - * @constructor Internal constructor, since replies are only meant to be generated upon receiving a remote reply - * or by calling [Query.reply] to reply to the specified [Query]. - * - * @param replierId The replierId of the remotely generated reply. - */ - class Success internal constructor(replierId: ZenohID?, val sample: Sample) : Reply(replierId) { - - /** - * Builder for the [Success] reply. - * - * @property query The [Query] to reply to. - * @property keyExpr The [KeyExpr] of the queryable. - * @property value The [Value] with the reply information. - */ - class Builder internal constructor(val query: Query, val keyExpr: KeyExpr, val value: Value) : - Resolvable { - - private val kind = SampleKind.PUT - private var timeStamp: TimeStamp? = null - private var attachment: ByteArray? = null - private var qosBuilder = QoS.Builder() - - /** - * Sets the [TimeStamp] of the replied [Sample]. - */ - fun timestamp(timeStamp: TimeStamp) = apply { this.timeStamp = timeStamp } - - /** - * Appends an attachment to the reply. - */ - fun attachment(attachment: ByteArray) = apply { this.attachment = attachment } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(express: Boolean) = apply { qosBuilder.express(express) } - - /** - * Sets the [Priority] of the reply. - */ - fun priority(priority: Priority) = apply { qosBuilder.priority(priority) } - - /** - * Sets the [CongestionControl] of the reply. - * - * @param congestionControl - */ - fun congestionControl(congestionControl: CongestionControl) = - apply { qosBuilder.congestionControl(congestionControl) } - - /** - * Constructs the reply sample with the provided parameters and triggers the reply to the query. - */ - @Throws(ZenohException::class) - override fun res() { - val sample = Sample(keyExpr, value, kind, timeStamp, qosBuilder.build(), attachment) - return query.reply(Success(null, sample)).res() - } - } + class Success internal constructor(replierId: ZenohId?, val sample: Sample) : Reply(replierId) { override fun toString(): String { return "Success(sample=$sample)" @@ -177,40 +45,20 @@ sealed class Reply private constructor(val replierId: ZenohID?) : ZenohType { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Success) return false - return sample == other.sample } override fun hashCode(): Int { return sample.hashCode() } + } /** * An Error reply. - * - * @property error: value with the error information. - * @constructor The constructor is private since reply instances are created through JNI when receiving a reply to a query. - * - * @param replierId: unique ID identifying the replier. */ - class Error internal constructor(replierId: ZenohID?, val error: Value) : Reply(replierId) { - - /** - * Builder for the [Error] reply. - * - * @property query The [Query] to reply to. - * @property value The [Value] with the reply information. - */ - class Builder internal constructor(val query: Query, val value: Value) : Resolvable { - - /** - * Triggers the error reply. - */ - override fun res() { - return query.reply(Error(null, value)).res() - } - } + class Error internal constructor(replierId: ZenohId?, val error: ZBytes, val encoding: Encoding) : + Reply(replierId) { override fun toString(): String { return "Error(error=$error)" @@ -227,64 +75,48 @@ sealed class Reply private constructor(val replierId: ZenohID?) : ZenohType { return error.hashCode() } } +} - /** - * A Delete reply. - * - * @property keyExpr - * @constructor - * - * @param replierId - */ - class Delete internal constructor( - replierId: ZenohID?, - val keyExpr: KeyExpr, - val timestamp: TimeStamp?, - val attachment: ByteArray?, - val qos: QoS - ) : Reply(replierId) { - - class Builder internal constructor(val query: Query, val keyExpr: KeyExpr) : Resolvable { - - private val kind = SampleKind.DELETE - private var timeStamp: TimeStamp? = null - private var attachment: ByteArray? = null - private var qosBuilder = QoS.Builder() - - /** - * Sets the [TimeStamp] of the replied [Sample]. - */ - fun timestamp(timeStamp: TimeStamp) = apply { this.timeStamp = timeStamp } - - /** - * Appends an attachment to the reply. - */ - fun attachment(attachment: ByteArray) = apply { this.attachment = attachment } - - /** - * Sets the express flag. If true, the reply won't be batched in order to reduce the latency. - */ - fun express(express: Boolean) = apply { qosBuilder.express(express) } +/** + * Options for performing a [Reply] to a [Query]. + * + * @param encoding [Encoding] of the payload of the reply. + * @param timeStamp Optional timestamp. + * @param attachment Optional attachment. + * @property express [QoS] express value. + * @property congestionControl The congestion control policy. + * @property priority The priority policy. + */ +data class ReplyOptions( + var encoding: Encoding = Encoding.defaultEncoding(), + var timeStamp: TimeStamp? = null, + var attachment: ZBytes? = null, + var express: Boolean = QoS.defaultQoS.express, + var congestionControl: CongestionControl = QoS.defaultQoS.congestionControl, + var priority: Priority = QoS.defaultQoS.priority +) - /** - * Sets the [Priority] of the reply. - */ - fun priority(priority: Priority) = apply { qosBuilder.priority(priority) } +/** + * Options for performing a Reply Delete to a [Query]. + * + * @param timeStamp Optional timestamp. + * @param attachment Optional attachment. + * @property express [QoS] express value. + * @property congestionControl The congestion control policy. + * @property priority The priority policy. + */ +data class ReplyDelOptions( + var timeStamp: TimeStamp? = null, + var attachment: ZBytes? = null, + var express: Boolean = QoS.defaultQoS.express, + var congestionControl: CongestionControl = QoS.defaultQoS.congestionControl, + var priority: Priority = QoS.defaultQoS.priority +) - /** - * Sets the [CongestionControl] of the reply. - * - * @param congestionControl - */ - fun congestionControl(congestionControl: CongestionControl) = - apply { qosBuilder.congestionControl(congestionControl) } - /** - * Triggers the delete reply. - */ - override fun res() { - return query.reply(Delete(null, keyExpr, timeStamp, attachment, qosBuilder.build())).res() - } - } - } -} \ No newline at end of file +/** + * Options for performing a Reply Err to a [Query]. + * + * @param encoding The encoding of the error message. + */ +data class ReplyErrOptions(var encoding: Encoding = Encoding.defaultEncoding()) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt new file mode 100644 index 00000000..34e5cd4f --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/query/Selector.kt @@ -0,0 +1,103 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.query + +import io.zenoh.exceptions.ZError +import io.zenoh.keyexpr.KeyExpr + +/** + * A selector is the combination of a [KeyExpr], which defines the + * set of keys that are relevant to an operation, and a set of parameters + * with a few intended uses: + * - specifying arguments to a queryable, allowing the passing of Remote Procedure Call parameters + * - filtering by value, + * - filtering by metadata, such as the timestamp of a value, + * - specifying arguments to zenoh when using the REST API. + * + * When in string form, selectors look a lot like a URI, with similar semantics: + * - the `key_expr` before the first `?` must be a valid key expression. + * - the `parameters` after the first `?` should be encoded like the query section of a URL: + * - parameters are separated by `;`, + * - the parameter name and value are separated by the first `=`, + * - in the absence of `=`, the parameter value is considered to be the empty string, + * - both name and value should use percent-encoding to escape characters, + * - defining a value for the same parameter name twice is considered undefined behavior, + * with the encouraged behaviour being to reject operations when a duplicate parameter is detected. + * + * Zenoh intends to standardize the usage of a set of parameter names. To avoid conflicting with RPC parameters, + * the Zenoh team has settled on reserving the set of parameter names that start with non-alphanumeric characters. + * + * The full specification for selectors is available [here](https://github.com/eclipse-zenoh/roadmap/tree/main/rfcs/ALL/Selectors), + * it includes standardized parameters. + * + * Queryable implementers are encouraged to prefer these standardized parameter names when implementing their + * associated features, and to prefix their own parameter names to avoid having conflicting parameter names with other + * queryables. + * + * @property keyExpr The [KeyExpr] of the selector. + * @property parameters The [Parameters] of the selector. + */ +data class Selector(val keyExpr: KeyExpr, val parameters: Parameters? = null) : AutoCloseable, IntoSelector { + + companion object { + + /** + * Try from. + * + * The default way to construct a Selector. + * + * When in string form, selectors look a lot like a URI, with similar semantics: + * - the `key_expr` before the first `?` must be a valid key expression. + * - the `parameters` after the first `?` should be encoded like the query section of a URL: + * - parameters are separated by `;`, + * - the parameter name and value are separated by the first `=`, + * - in the absence of `=`, the parameter value is considered to be the empty string, + * - both name and value should use percent-encoding to escape characters, + * - defining a value for the same parameter name twice is considered undefined behavior, + * with the encouraged behaviour being to reject operations when a duplicate parameter is detected. + * + * @param selector The selector expression as a String. + * @return An instance [Selector]. + * @throws ZError in case of failure processing the selector. + */ + @Throws(ZError::class) + @JvmStatic + fun tryFrom(selector: String): Selector { + if (selector.isEmpty()) { + throw ZError("Attempting to create a selector from an empty string.") + } + val result = selector.split('?', limit = 2) + val keyExpr = KeyExpr.autocanonize(result[0]) + val params = if (result.size == 2) Parameters.from(result[1]) else null + + return Selector(keyExpr, params) + } + } + + override fun into(): Selector = this + + override fun toString(): String { + return parameters?.let { "$keyExpr?$parameters" } ?: keyExpr.toString() + } + + /** Closes the selector's [KeyExpr]. */ + override fun close() { + keyExpr.close() + } +} + +interface IntoSelector { + fun into(): Selector +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Query.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Query.kt deleted file mode 100644 index cd4b7c80..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Query.kt +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.queryable - -import io.zenoh.Resolvable -import io.zenoh.ZenohType -import io.zenoh.selector.Selector -import io.zenoh.value.Value -import io.zenoh.exceptions.SessionException -import io.zenoh.jni.JNIQuery -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.query.Reply - -/** - * Represents a Zenoh Query in Kotlin. - * - * A Query is generated within the context of a [Queryable], when receiving a [Query] request. - * - * @property keyExpr The key expression to which the query is associated. - * @property selector The selector - * @property value Optional value in case the received query was declared using "with query". - * @property attachment Optional attachment. - * @property jniQuery Delegate object in charge of communicating with the underlying native code. - * @constructor Instances of Query objects are only meant to be created through the JNI upon receiving - * a query request. Therefore, the constructor is private. - */ -class Query internal constructor( - val keyExpr: KeyExpr, - val selector: Selector, - val value: Value?, - val attachment: ByteArray?, - private var jniQuery: JNIQuery? -) : AutoCloseable, ZenohType { - - /** Shortcut to the [selector]'s parameters. */ - val parameters = selector.parameters - - /** - * Reply to the specified key expression. - * - * @param keyExpr Key expression to reply to. This parameter must not be necessarily the same - * as the key expression from the Query, however it must intersect with the query key. - * @return a [Reply.Builder] - */ - fun reply(keyExpr: KeyExpr) = Reply.Builder(this, keyExpr) - - override fun close() { - jniQuery?.apply { - this.close() - jniQuery = null - } - } - - @Suppress("removal") - protected fun finalize() { - close() - } - - /** - * Perform a reply operation to the remote [Query]. - * - * A query can not be replied more than once. After the reply is performed, the query is considered - * to be no more valid and further attempts to reply to it will fail. - * - * @param reply The [Reply] to the Query. - * @return A [Resolvable] that returns a [Result] with the status of the reply operation. - */ - internal fun reply(reply: Reply): Resolvable = Resolvable { - jniQuery?.apply { - val result = when (reply) { - is Reply.Success -> { - replySuccess(reply.sample) - } - is Reply.Error -> { - replyError(reply.error) - } - is Reply.Delete -> { - replyDelete(reply.keyExpr, reply.timestamp, reply.attachment, reply.qos) - } - } - jniQuery = null - return@Resolvable result - } - throw(SessionException("Query is invalid")) - } -} - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Queryable.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Queryable.kt deleted file mode 100644 index 1c13f44d..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/queryable/Queryable.kt +++ /dev/null @@ -1,176 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.queryable - -import io.zenoh.* -import io.zenoh.exceptions.ZenohException -import io.zenoh.handlers.Callback -import io.zenoh.handlers.BlockingQueueHandler -import io.zenoh.handlers.Handler -import io.zenoh.jni.JNIQueryable -import io.zenoh.keyexpr.KeyExpr -import java.util.* -import java.util.concurrent.BlockingQueue -import java.util.concurrent.LinkedBlockingDeque - -/** - * A queryable that allows to perform multiple queries on the specified [KeyExpr]. - * - * Its main purpose is to keep the queryable active as long as it exists. - * - * Example using the default [BlockingQueueHandler] handler: - * ```java - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example/zenoh-java-queryable")) { - * System.out.println("Declaring Queryable"); - * try (Queryable>> queryable = session.declareQueryable(keyExpr).res()) { - * BlockingQueue> receiver = queryable.getReceiver(); - * while (true) { - * Optional wrapper = receiver.take(); - * if (wrapper.isEmpty()) { - * break; - * } - * Query query = wrapper.get(); - * String valueInfo = query.getValue() != null ? " with value '" + query.getValue() + "'" : ""; - * System.out.println(">> [Queryable] Received Query '" + query.getSelector() + "'" + valueInfo); - * try { - * query.reply(keyExpr) - * .success("Queryable from Java!") - * .withKind(SampleKind.PUT) - * .withTimeStamp(TimeStamp.getCurrentTime()) - * .res(); - * } catch (Exception e) { - * System.out.println(">> [Queryable] Error sending reply: " + e); - * } - * } - * } - * } - * } - * ``` - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, [R] will be [Unit]. - * @property keyExpr The [KeyExpr] to which the subscriber is associated. - * @property receiver Optional [R] that is provided when specifying a [Handler] for the subscriber. - * @property jniQueryable Delegate object in charge of communicating with the underlying native code. - * @constructor Internal constructor. Instances of Queryable must be created through the [Builder] obtained after - * calling [Session.declareQueryable] or alternatively through [newBuilder]. - */ -class Queryable internal constructor( - val keyExpr: KeyExpr, val receiver: R?, private var jniQueryable: JNIQueryable? -) : AutoCloseable, SessionDeclaration { - - override fun isValid(): Boolean { - return jniQueryable != null - } - - override fun undeclare() { - jniQueryable?.close() - jniQueryable = null - } - - override fun close() { - undeclare() - } - - protected fun finalize() { - jniQueryable?.close() - } - - companion object { - - /** - * Creates a new [Builder] associated to the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the queryable will be declared. - * @param keyExpr The [KeyExpr] associated to the queryable. - * @return An empty [Builder] with a default [BlockingQueueHandler] to handle the incoming samples. - */ - fun newBuilder(session: Session, keyExpr: KeyExpr): Builder>> { - return Builder(session, keyExpr, handler = BlockingQueueHandler(queue = LinkedBlockingDeque())) - } - } - - /** - * Builder to construct a [Queryable]. - * - * Either a [Handler] or a [Callback] must be specified. Note neither of them are stackable and are mutually exclusive, - * meaning that it is not possible to specify multiple callbacks and/or handlers, the builder only considers the - * last one specified. - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, R will be [Unit]. - * @property session [Session] to which the [Queryable] will be bound to. - * @property keyExpr The [KeyExpr] to which the queryable is associated. - * @property callback Optional callback that will be triggered upon receiving a [Query]. - * @property handler Optional handler to receive the incoming queries. - * @constructor Creates a Builder. This constructor is internal and should not be called directly. Instead, this - * builder should be obtained through the [Session] after calling [Session.declareQueryable]. - */ - class Builder internal constructor( - private val session: Session, - private val keyExpr: KeyExpr, - private var callback: Callback? = null, - private var handler: Handler? = null - ): Resolvable> { - private var complete: Boolean = false - private var onClose: (() -> Unit)? = null - - private constructor(other: Builder<*>, handler: Handler?) : this(other.session, other.keyExpr) { - this.handler = handler - this.complete = other.complete - this.onClose = other.onClose - } - - private constructor(other: Builder<*>, callback: Callback?) : this(other.session, other.keyExpr) { - this.callback = callback - this.complete = other.complete - this.onClose = other.onClose - } - - /** Change queryable completeness. */ - fun complete(complete: Boolean) = apply { this.complete = complete } - - /** Specify an action to be invoked when the [Queryable] is undeclared. */ - fun onClose(action: () -> Unit): Builder { - this.onClose = action - return this - } - - /** Specify a [Callback]. Overrides any previously specified callback or handler. */ - fun with(callback: Callback): Builder = Builder(this, callback) - - /** Specify a [Handler]. Overrides any previously specified callback or handler. */ - fun with(handler: Handler): Builder = Builder(this, handler) - - /** Specify a [BlockingQueue]. Overrides any previously specified callback or handler. */ - fun with(blockingQueue: BlockingQueue>): Builder>> = Builder(this, BlockingQueueHandler(blockingQueue)) - - /** - * Resolve the builder, creating a [Queryable] with the provided parameters. - * - * @return The newly created [Queryable]. - */ - @Throws(ZenohException::class) - override fun res(): Queryable { - require(callback != null || handler != null) { "Either a callback or a handler must be provided." } - val resolvedCallback = callback ?: Callback { t: Query -> handler?.handle(t) } - val resolvedOnClose = fun() { - handler?.onClose() - onClose?.invoke() - } - return session.run { resolveQueryable(keyExpr, resolvedCallback, resolvedOnClose, handler?.receiver(), complete) } - } - } -} - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt index e9420678..216d67e0 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/Sample.kt @@ -15,54 +15,37 @@ package io.zenoh.sample import io.zenoh.ZenohType -import io.zenoh.prelude.SampleKind -import io.zenoh.prelude.QoS +import io.zenoh.qos.QoS import io.zenoh.keyexpr.KeyExpr -import io.zenoh.value.Value +import io.zenoh.bytes.Encoding +import io.zenoh.bytes.ZBytes import org.apache.commons.net.ntp.TimeStamp /** * Class representing a Zenoh Sample. * - * A sample consists of a [KeyExpr]-[Value] pair, annotated with the [SampleKind] (PUT or DELETE) of the publication - * used to emit it and a timestamp. - * * @property keyExpr The [KeyExpr] of the sample. - * @property value The [Value] of the sample. + * @property payload [ZBytes] with the payload of the sample. + * @property encoding [Encoding] of the payload. * @property kind The [SampleKind] of the sample. * @property timestamp Optional [TimeStamp]. * @property qos The Quality of Service settings used to deliver the sample. * @property attachment Optional attachment. + * @property express [QoS] express value. + * @property congestionControl The congestion control policy. + * @property priority The priority policy. */ -class Sample( +data class Sample( val keyExpr: KeyExpr, - val value: Value, + val payload: ZBytes, + val encoding: Encoding, val kind: SampleKind, val timestamp: TimeStamp?, val qos: QoS, - val attachment: ByteArray? = null + val attachment: ZBytes? = null, ): ZenohType { - override fun toString(): String { - return if (kind == SampleKind.DELETE) "$kind($keyExpr)" else "$kind($keyExpr: $value)" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Sample - - if (keyExpr != other.keyExpr) return false - if (value != other.value) return false - if (kind != other.kind) return false - return timestamp == other.timestamp - } - override fun hashCode(): Int { - var result = keyExpr.hashCode() - result = 31 * result + value.hashCode() - result = 31 * result + kind.hashCode() - result = 31 * result + (timestamp?.hashCode() ?: 0) - return result - } + val express = qos.express + val congestionControl = qos.congestionControl + val priority = qos.priority } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/SampleKind.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt similarity index 96% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/SampleKind.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt index 6e057c50..cea02a02 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/prelude/SampleKind.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/sample/SampleKind.kt @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // -package io.zenoh.prelude +package io.zenoh.sample /** The kind of sample. */ enum class SampleKind { diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Hello.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Hello.kt new file mode 100644 index 00000000..ec9ccb82 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Hello.kt @@ -0,0 +1,29 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.scouting + +import io.zenoh.ZenohType +import io.zenoh.config.WhatAmI +import io.zenoh.config.ZenohId + +/** + * Hello message received while scouting. + * + * @property whatAmI [WhatAmI] configuration: it indicates the role of the zenoh node sending the HELLO message. + * @property zid [ZenohId] of the node sending the hello message. + * @property locators The locators of this hello message. + * @see Scout + */ +data class Hello(val whatAmI: WhatAmI, val zid: ZenohId, val locators: List): ZenohType diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt new file mode 100644 index 00000000..8034a851 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/Scout.kt @@ -0,0 +1,109 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.scouting + +import io.zenoh.jni.JNIScout + +/** + * Scout for routers and/or peers. + * + * Scout spawns a task that periodically sends scout messages and waits for Hello replies. + * Drop the returned Scout to stop the scouting task. + * + * To launch a scout, use [io.zenoh.Zenoh.scout]: + * + * Example using the default blocking queue handler: + * ```java + * + * var scoutOptions = new ScoutOptions(); + * scoutOptions.setWhatAmI(Set.of(WhatAmI.Peer, WhatAmI.Router)); + * + * var scout = Zenoh.scout(scoutOptions); + * BlockingQueue> receiver = scout.getReceiver(); + * + * try { + * while (true) { + * Optional wrapper = receiver.take(); + * if (wrapper.isEmpty()) { + * break; + * } + * + * Hello hello = wrapper.get(); + * System.out.println(hello); + * } + * } finally { + * scout.stop(); + * } + * ``` + * + * Example using a callback: + * ```java + * var scoutOptions = new ScoutOptions(); + * scoutOptions.setWhatAmI(Set.of(WhatAmI.Peer, WhatAmI.Router)); + * Zenoh.scout(hello -> { + * //... + * System.out.println(hello); + * }, scoutOptions); + * ``` + * + * @see CallbackScout + * @see HandlerScout + */ +sealed class Scout ( + private var jniScout: JNIScout? +) : AutoCloseable { + + /** + * Stops the scouting. + */ + fun stop() { + jniScout?.close() + jniScout = null + } + + /** + * Equivalent to [stop]. + */ + override fun close() { + stop() + } + + protected fun finalize() { + stop() + } +} + +/** + * Scout using a callback to handle incoming [Hello] messages. + * + * Example: + * ```java + * CallbackScout scout = Zenoh.scout(hello -> {...}); + * ``` + */ +class CallbackScout internal constructor(jniScout: JNIScout?) : Scout(jniScout) + +/** + * Scout using a handler to handle incoming [Hello] messages. + * + * Example + * ```java + * HandlerScout>> scout = Zenoh.scout(); + * ``` + * + * @param R The type of the receiver. + * @param receiver The receiver of the scout's handler. + */ +class HandlerScout internal constructor(jniScout: JNIScout?, val receiver: R) : Scout(jniScout) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIException.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/ScoutOptions.kt similarity index 60% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIException.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/ScoutOptions.kt index dd06cda7..3f3f9f13 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/exceptions/JNIException.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/scouting/ScoutOptions.kt @@ -12,12 +12,18 @@ // ZettaScale Zenoh Team, // -package io.zenoh.exceptions +package io.zenoh.scouting + +import io.zenoh.Config +import io.zenoh.config.WhatAmI /** - * JNI (Java native interface) exception. + * Options for scouting. * - * This type of exception is thrown from the native code when something goes wrong regarding the - * communication between the Java/Kotlin layer and the native layer through the JNI. + * @param config A [Config] for scouting. + * @param whatAmI [WhatAmI] parameters. */ -class JNIException(msg: String?) : ZenohException(msg) +data class ScoutOptions( + var config: Config? = null, + var whatAmI: Set = setOf(WhatAmI.Peer, WhatAmI.Router) +) diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/IntoSelector.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/IntoSelector.kt deleted file mode 100644 index fe47ac68..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/IntoSelector.kt +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.selector - -import io.zenoh.exceptions.KeyExprException -import io.zenoh.keyexpr.KeyExpr - -@Throws(KeyExprException::class) -fun String.intoSelector(): Selector { - if (this.isEmpty()) { - throw(KeyExprException("Attempting to create a KeyExpr from an empty string.")) - } - val result = this.split('?', limit = 2) - val keyExpr = KeyExpr.autocanonize(result[0]) - val params = if (result.size == 2) result[1] else "" - return Selector(keyExpr, params) -} - diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/Selector.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/Selector.kt deleted file mode 100644 index 133bcc04..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/selector/Selector.kt +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.selector - -import io.zenoh.exceptions.KeyExprException -import io.zenoh.keyexpr.KeyExpr - -/** - * A selector is the combination of a [KeyExpr], which defines the - * set of keys that are relevant to an operation, and a [parameters], a set of key-value pairs with a few uses: - * - * * specifying arguments to a queryable, allowing the passing of Remote Procedure Call parameters - * * filtering by value, - * * filtering by metadata, such as the timestamp of a value - * - * @property keyExpr The [KeyExpr] of the selector. - * @property parameters The parameters of the selector. - */ -class Selector(val keyExpr: KeyExpr, val parameters: String = ""): AutoCloseable { - - companion object { - - /** - * Try from. - * - * Equivalent constructor to [String.intoSelector], generates a selector from a string. - * - * @param expression A string with the form "?". - * @return A [Selector] in case of success. - * @throws KeyExprException in case of failure generating the key expression. - */ - @JvmStatic - @Throws(KeyExprException::class) - fun tryFrom(expression: String): Selector { - if (expression.isEmpty()) { - throw(KeyExprException("Attempting to create a KeyExpr from an empty string.")) - } - val result = expression.split('?', limit = 2) - val keyExpr = KeyExpr.autocanonize(result[0]) - val params = if (result.size == 2) result[1] else "" - return Selector(keyExpr, params) - } - } - - override fun toString(): String { - return if (parameters.isEmpty()) "$keyExpr" else "$keyExpr?$parameters" - } - - /** Closes the selector's [KeyExpr]. */ - override fun close() { - keyExpr.close() - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/SessionDeclaration.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionDeclaration.kt similarity index 69% rename from zenoh-java/src/commonMain/kotlin/io/zenoh/SessionDeclaration.kt rename to zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionDeclaration.kt index 1c747e40..2c3a9224 100644 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/SessionDeclaration.kt +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionDeclaration.kt @@ -12,19 +12,16 @@ // ZettaScale Zenoh Team, // -package io.zenoh +package io.zenoh.session /** * Session declaration. * - * A session declaration is either a [io.zenoh.publication.Publisher], - * a [io.zenoh.subscriber.Subscriber] or a [io.zenoh.queryable.Queryable] declared from a [Session]. + * A session declaration is either a [io.zenoh.pubsub.Publisher], + * a [io.zenoh.pubsub.Subscriber] or a [io.zenoh.query.Queryable] declared from a [io.zenoh.Session]. */ interface SessionDeclaration { - /** Returns true if the declaration has not been undeclared. */ - fun isValid(): Boolean - /** Undeclare a declaration. No further operations should be performed after calling this function. */ fun undeclare() } diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionInfo.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionInfo.kt new file mode 100644 index 00000000..9215daa4 --- /dev/null +++ b/zenoh-java/src/commonMain/kotlin/io/zenoh/session/SessionInfo.kt @@ -0,0 +1,49 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh.session + +import io.zenoh.Session +import io.zenoh.config.ZenohId +import io.zenoh.exceptions.ZError + +/** + * Class allowing to obtain the information of a [Session]. + */ +class SessionInfo(private val session: Session) { + + /** + * Return the [ZenohId] of the current Zenoh [Session] + */ + @Throws(ZError::class) + fun zid(): ZenohId { + return session.zid() + } + + /** + * Return the [ZenohId] of the zenoh peers the session is currently connected to. + */ + @Throws(ZError::class) + fun peersZid(): List { + return session.getPeersId() + } + + /** + * Return the [ZenohId] of the zenoh routers the session is currently connected to. + */ + @Throws(ZError::class) + fun routersZid(): List { + return session.getRoutersId() + } +} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Subscriber.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Subscriber.kt deleted file mode 100644 index 53b41f3a..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/subscriber/Subscriber.kt +++ /dev/null @@ -1,184 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.subscriber - -import io.zenoh.* -import io.zenoh.exceptions.ZenohException -import io.zenoh.handlers.Callback -import io.zenoh.handlers.BlockingQueueHandler -import io.zenoh.handlers.Handler -import io.zenoh.subscriber.Subscriber.Builder -import io.zenoh.jni.JNISubscriber -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.sample.Sample -import java.util.* -import java.util.concurrent.BlockingQueue -import java.util.concurrent.LinkedBlockingDeque - -/** - * A subscriber that allows listening to updates on a key expression and reacting to changes. - * - * Its main purpose is to keep the subscription active as long as it exists. - * - * Example using the default [BlockingQueueHandler] handler: - * - * ```java - * System.out.println("Opening session..."); - * try (Session session = Session.open()) { - * try (KeyExpr keyExpr = KeyExpr.tryFrom("demo/example")) { - * System.out.println("Declaring Subscriber on '" + keyExpr + "'..."); - * try (Subscriber>> subscriber = session.declareSubscriber(keyExpr).res()) { - * BlockingQueue> receiver = subscriber.getReceiver(); - * assert receiver != null; - * while (true) { - * Optional wrapper = receiver.take(); - * if (wrapper.isEmpty()) { - * break; - * } - * Sample sample = wrapper.get(); - * System.out.println(">> [Subscriber] Received " + sample.getKind() + " ('" + sample.getKeyExpr() + "': '" + sample.getValue() + "')"); - * } - * } - * } - * } - * ``` - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, R will be [Unit]. - * @property keyExpr The [KeyExpr] to which the subscriber is associated. - * @property receiver Optional [R] that is provided when specifying a [Handler] for the subscriber. - * @property jniSubscriber Delegate object in charge of communicating with the underlying native code. - * @constructor Internal constructor. Instances of Subscriber must be created through the [Builder] obtained after - * calling [Session.declareSubscriber] or alternatively through [newBuilder]. - */ -class Subscriber internal constructor( - val keyExpr: KeyExpr, val receiver: R?, private var jniSubscriber: JNISubscriber? -) : AutoCloseable, SessionDeclaration { - - override fun isValid(): Boolean { - return jniSubscriber != null - } - - override fun undeclare() { - jniSubscriber?.close() - jniSubscriber = null - } - - override fun close() { - undeclare() - } - - protected fun finalize() { - jniSubscriber?.close() - } - - companion object { - - /** - * Creates a new [Builder] associated to the specified [session] and [keyExpr]. - * - * @param session The [Session] from which the subscriber will be declared. - * @param keyExpr The [KeyExpr] associated to the subscriber. - * @return An empty [Builder] with a default [BlockingQueueHandler] to handle the incoming samples. - */ - fun newBuilder(session: Session, keyExpr: KeyExpr): Builder>> { - return Builder(session, keyExpr, handler = BlockingQueueHandler(queue = LinkedBlockingDeque())) - } - } - - /** - * Builder to construct a [Subscriber]. - * - * Either a [Handler] or a [Callback] must be specified. Note neither of them are stackable and are mutually exclusive, - * meaning that it is not possible to specify multiple callbacks and/or handlers, the builder only considers the - * last one specified. - * - * @param R Receiver type of the [Handler] implementation. If no handler is provided to the builder, R will be [Unit]. - * @property session [Session] to which the [Subscriber] will be bound to. - * @property keyExpr The [KeyExpr] to which the subscriber is associated. - * @constructor Creates a Builder. This constructor is internal and should not be called directly. Instead, this - * builder should be obtained through the [Session] after calling [Session.declareSubscriber]. - */ - class Builder internal constructor( - private val session: Session, - private val keyExpr: KeyExpr, - private var callback: Callback? = null, - private var handler: Handler? = null - ): Resolvable> { - - private var reliability: Reliability = Reliability.BEST_EFFORT - private var onClose: (() -> Unit)? = null - - private constructor(other: Builder<*>, handler: Handler?): this(other.session, other.keyExpr) { - this.handler = handler - copyParams(other) - } - - private constructor(other: Builder<*>, callback: Callback?) : this(other.session, other.keyExpr) { - this.callback = callback - copyParams(other) - } - - private fun copyParams(other: Builder<*>) { - this.reliability = other.reliability - this.onClose = other.onClose - } - - /** Sets the [Reliability]. */ - fun reliability(reliability: Reliability): Builder = apply { - this.reliability = reliability - } - - /** Sets the reliability to [Reliability.RELIABLE]. */ - fun reliable(): Builder = apply { - this.reliability = Reliability.RELIABLE - } - - /** Sets the reliability to [Reliability.BEST_EFFORT]. */ - fun bestEffort(): Builder = apply { - this.reliability = Reliability.BEST_EFFORT - } - - /** Specify an action to be invoked when the [Subscriber] is undeclared. */ - fun onClose(action: () -> Unit): Builder { - this.onClose = action - return this - } - - /** Specify a [Callback]. Overrides any previously specified callback or handler. */ - fun with(callback: Callback): Builder = Builder(this, callback) - - /** Specify a [Handler]. Overrides any previously specified callback or handler. */ - fun with(handler: Handler): Builder = Builder(this, handler) - - /** Specify a [BlockingQueue]. Overrides any previously specified callback or handler. */ - fun with(blockingQueue: BlockingQueue>): Builder>> = Builder(this, BlockingQueueHandler(blockingQueue)) - - /** - * Resolve the builder, creating a [Subscriber] with the provided parameters. - * - * @return The newly created [Subscriber]. - */ - @Throws(ZenohException::class) - override fun res(): Subscriber { - require(callback != null || handler != null) { "Either a callback or a handler must be provided." } - val resolvedCallback = callback ?: Callback { t: Sample -> handler?.handle(t) } - val resolvedOnClose = fun() { - handler?.onClose() - onClose?.invoke() - } - return session.run { resolveSubscriber(keyExpr, resolvedCallback, resolvedOnClose, handler?.receiver(), reliability) } - } - } -} diff --git a/zenoh-java/src/commonMain/kotlin/io/zenoh/value/Value.kt b/zenoh-java/src/commonMain/kotlin/io/zenoh/value/Value.kt deleted file mode 100644 index ba787bcb..00000000 --- a/zenoh-java/src/commonMain/kotlin/io/zenoh/value/Value.kt +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh.value - -import io.zenoh.prelude.Encoding - -/** - * A Zenoh value. - * - * A Value is a pair of a binary payload, and a mime-type-like encoding string. - * - * @property payload The payload of this Value. - * @property encoding An encoding description indicating how the associated payload is encoded. - */ -class Value(val payload: ByteArray, val encoding: Encoding) { - - /** - * Constructs a value with the provided message, using [Encoding.ID.TEXT_PLAIN] for encoding. - */ - constructor(message: String): this(message.toByteArray(), Encoding(Encoding.ID.TEXT_PLAIN)) - - /** - * Constructs a value with the provided message and encoding. - */ - constructor(message: String, encoding: Encoding): this(message.toByteArray(), encoding) - - companion object { - - /** Return an empty value. */ - fun empty(): Value { - return Value(ByteArray(0), Encoding(Encoding.ID.ZENOH_BYTES)) - } - } - - override fun toString(): String { - return payload.decodeToString() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Value - - if (!payload.contentEquals(other.payload)) return false - return encoding == other.encoding - } - - override fun hashCode(): Int { - var result = payload.contentHashCode() - result = 31 * result + encoding.hashCode() - return result - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/DeleteTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/DeleteTest.kt deleted file mode 100644 index 083e210a..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/DeleteTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.SampleKind -import io.zenoh.sample.Sample -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class DeleteTest { - - @Test - fun delete_isProperlyReceivedBySubscriber() { - val session = Session.open() - var receivedSample: Sample? = null - val keyExpr = "example/testing/keyexpr".intoKeyExpr() - val subscriber = session.declareSubscriber(keyExpr).with { sample -> receivedSample = sample }.res() - session.delete(keyExpr).res() - subscriber.close() - keyExpr.close() - session.close() - assertNotNull(receivedSample) - assertEquals(receivedSample!!.kind, SampleKind.DELETE) - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/EncodingTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/EncodingTest.kt deleted file mode 100644 index 5f344e06..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/EncodingTest.kt +++ /dev/null @@ -1,168 +0,0 @@ -package io.zenoh - -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.Encoding -import io.zenoh.query.Reply -import io.zenoh.sample.Sample -import io.zenoh.value.Value -import kotlin.test.* - -class EncodingTest { - - @Test - fun encoding_subscriberTest() { - val session = Session.open() - val keyExpr = "example/testing/keyexpr".intoKeyExpr() - - // Testing non null schema - var receivedSample: Sample? = null - val subscriber = session.declareSubscriber(keyExpr).with { sample -> - receivedSample = sample - }.res() - var value = Value("test", Encoding(Encoding.ID.TEXT_CSV, "test_schema")) - session.put(keyExpr, value).res() - Thread.sleep(200) - - assertNotNull(receivedSample) - assertEquals(Encoding.ID.TEXT_CSV, receivedSample!!.value.encoding.id) - assertEquals("test_schema", receivedSample!!.value.encoding.schema) - - // Testing null schema - receivedSample = null - value = Value("test2", Encoding(Encoding.ID.ZENOH_STRING, null)) - session.put(keyExpr, value).res() - Thread.sleep(200) - - assertNotNull(receivedSample) - assertEquals(Encoding.ID.ZENOH_STRING, receivedSample!!.value.encoding.id) - assertNull(receivedSample!!.value.encoding.schema) - - subscriber.close() - session.close() - } - - @Test - fun encoding_replySuccessTest() { - val session = Session.open() - val keyExpr = "example/testing/**".intoKeyExpr() - val test1 = "example/testing/reply_success".intoKeyExpr() - val test2 = "example/testing/reply_success_with_schema".intoKeyExpr() - - val testValueA = Value("test", Encoding(Encoding.ID.TEXT_CSV, null)) - val testValueB = Value("test", Encoding(Encoding.ID.TEXT_CSV, "test_schema")) - - val queryable = session.declareQueryable(keyExpr).with { query -> - when (query.keyExpr) { - test1 -> query.reply(query.keyExpr).success(testValueA).res() - test2 -> query.reply(query.keyExpr).success(testValueB).res() - } - }.res() - - // Testing with null schema on a reply success scenario. - var receivedSample: Sample? = null - session.get(test1).with { reply -> - assertTrue(reply is Reply.Success) - receivedSample = reply.sample - }.res() - Thread.sleep(200) - - assertNotNull(receivedSample) - assertEquals(Encoding.ID.TEXT_CSV, receivedSample!!.value.encoding.id) - assertNull(receivedSample!!.value.encoding.schema) - - // Testing with non-null schema on a reply success scenario. - receivedSample = null - session.get(test2).with { reply -> - assertTrue(reply is Reply.Success) - receivedSample = reply.sample - }.res() - Thread.sleep(200) - - assertNotNull(receivedSample) - assertEquals(Encoding.ID.TEXT_CSV, receivedSample!!.value.encoding.id) - assertEquals("test_schema", receivedSample!!.value.encoding.schema) - - queryable.close() - session.close() - } - - @Test - fun encoding_replyErrorTest() { - val session = Session.open() - val keyExpr = "example/testing/**".intoKeyExpr() - - val test1 = "example/testing/reply_error".intoKeyExpr() - val test2 = "example/testing/reply_error_with_schema".intoKeyExpr() - - val testValueA = Value("test", Encoding(Encoding.ID.TEXT_CSV, null)) - val testValueB = Value("test", Encoding(Encoding.ID.TEXT_CSV, "test_schema")) - - val queryable = session.declareQueryable(keyExpr).with { query -> - when (query.keyExpr) { - test1 -> query.reply(query.keyExpr).error(testValueA).res() - test2 -> query.reply(query.keyExpr).error(testValueB).res() - } - }.res() - - // Testing with null schema on a reply error scenario. - var errorValue: Value? = null - session.get(test1).with { reply -> - assertTrue(reply is Reply.Error) - errorValue = reply.error - }.res() - Thread.sleep(200) - - assertNotNull(errorValue) - assertEquals(Encoding.ID.TEXT_CSV, errorValue!!.encoding.id) - assertNull(errorValue!!.encoding.schema) - - // Testing with non-null schema on a reply error scenario. - errorValue = null - session.get(test2).with { reply -> - assertTrue(reply is Reply.Error) - errorValue = reply.error - }.res() - Thread.sleep(200) - - assertNotNull(errorValue) - assertEquals(Encoding.ID.TEXT_CSV, errorValue!!.encoding.id) - assertEquals("test_schema", errorValue!!.encoding.schema) - - queryable.close() - session.close() - } - - @Test - fun encoding_queryTest() { - val session = Session.open() - val keyExpr = "example/testing/keyexpr".intoKeyExpr() - val testValueA = Value("test", Encoding(Encoding.ID.TEXT_CSV, null)) - val testValueB = Value("test", Encoding(Encoding.ID.TEXT_CSV, "test_schema")) - - var receivedValue: Value? = null - val queryable = session.declareQueryable(keyExpr).with { query -> - receivedValue = query.value - query.close() - }.res() - - // Testing with null schema - session.get(keyExpr).withValue(testValueA).res() - Thread.sleep(200) - - assertNotNull(receivedValue) - assertEquals(Encoding.ID.TEXT_CSV, receivedValue!!.encoding.id) - assertNull(receivedValue!!.encoding.schema) - - // Testing non-null schema - receivedValue = null - session.get(keyExpr).withValue(testValueB).res() - Thread.sleep(200) - - assertNotNull(receivedValue) - assertEquals(Encoding.ID.TEXT_CSV, receivedValue!!.encoding.id) - assertEquals("test_schema", receivedValue!!.encoding.schema) - - queryable.close() - session.close() - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/GetTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/GetTest.kt deleted file mode 100644 index 89d645b0..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/GetTest.kt +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.handlers.Handler -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.SampleKind -import io.zenoh.query.Reply -import io.zenoh.queryable.Queryable -import io.zenoh.selector.Selector -import io.zenoh.value.Value -import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration -import java.util.* -import kotlin.test.* - -class GetTest { - - companion object { - val value = Value("Test") - val timestamp = TimeStamp.getCurrentTime() - val kind = SampleKind.PUT - } - - private lateinit var session: Session - private lateinit var keyExpr: KeyExpr - private lateinit var queryable: Queryable - - @BeforeTest - fun setUp() { - session = Session.open() - keyExpr = "example/testing/keyexpr".intoKeyExpr() - queryable = session.declareQueryable(keyExpr).with { query -> - query.reply(query.keyExpr) - .success(value) - .timestamp(timestamp) - .res() - }.res() - } - - @AfterTest - fun tearDown() { - keyExpr.close() - queryable.close() - session.close() - } - - @Test - fun get_runsWithCallback() { - var reply: Reply? = null - session.get(keyExpr).with { reply = it }.timeout(Duration.ofMillis(1000)).res() - - assertTrue(reply is Reply.Success) - val sample = (reply as Reply.Success).sample - assertEquals(value, sample.value) - assertEquals(kind, sample.kind) - assertEquals(keyExpr, sample.keyExpr) - assertEquals(timestamp, sample.timestamp) - } - - @Test - fun get_runsWithHandler() { - val receiver: ArrayList = session.get(keyExpr).with(TestHandler()) - .timeout(Duration.ofMillis(1000)).res()!! - - for (reply in receiver) { - reply as Reply.Success - val receivedSample = reply.sample - assertEquals(value, receivedSample.value) - assertEquals(SampleKind.PUT, receivedSample.kind) - assertEquals(timestamp, receivedSample.timestamp) - } - } - - @Test - fun getWithSelectorParamsTest() { - var receivedParams = String() - val queryable = session.declareQueryable(keyExpr).with { - it.use { query -> - receivedParams = query.parameters - } - }.res() - - val params = "arg1=val1,arg2=val2" - val selector = Selector(keyExpr, params) - session.get(selector).with {}.timeout(Duration.ofMillis(1000)).res() - Thread.sleep(1000) - - queryable.close() - assertEquals(params, receivedParams) - } -} - -/** A dummy handler for get operations. */ -private class TestHandler : Handler> { - - val performedReplies: ArrayList = ArrayList() - - override fun handle(t: Reply) { - performedReplies.add(t) - } - - override fun receiver(): ArrayList { - return performedReplies - } - - override fun onClose() {} -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt deleted file mode 100644 index bb0b0ec2..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt +++ /dev/null @@ -1,126 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.exceptions.SessionException -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import org.junit.Assert.assertThrows -import kotlin.test.* - -class KeyExprTest { - - init { - Zenoh.load() - } - - @Test - fun creation_TryFromTest() { - // A couple of examples of valid and invalid key expressions. - - KeyExpr.tryFrom("example/test") // Should not throw exception - - assertThrows(Exception::class.java) { KeyExpr.tryFrom("example/test?param='test'") } - - KeyExpr.tryFrom("example/*/test") // Should not throw exception - - assertThrows(Exception::class.java) { KeyExpr.tryFrom("example/!*/test") } - } - - @Test - fun equalizationTest() { - val keyExpr1 = KeyExpr.tryFrom("example/test") - val keyExpr2 = KeyExpr.tryFrom("example/test") - - assertEquals(keyExpr1, keyExpr2) - - val keyExpr3 = KeyExpr.tryFrom("different/key/expr") - assertNotEquals(keyExpr1, keyExpr3) - - // Despite being undeclared, the equals operation should still work. - keyExpr2.close() - assertEquals(keyExpr1, keyExpr2) - - keyExpr1.close() - assertEquals(keyExpr1, keyExpr2) - } - - @Test - fun creation_autocanonizeTest() { - val keyExpr1 = KeyExpr.autocanonize("example/**/test") - val keyExpr2 = KeyExpr.autocanonize("example/**/**/test") - assertEquals(keyExpr1, keyExpr2) - } - - @Test - fun intersectionTest() { - val keyExprA = KeyExpr.tryFrom("example/*/test") - - val keyExprB = KeyExpr.tryFrom("example/B/test") - assertTrue(keyExprA.intersects(keyExprB)) - - val keyExprC = KeyExpr.tryFrom("example/B/C/test") - assertFalse(keyExprA.intersects(keyExprC)) - - val keyExprA2 = KeyExpr.tryFrom("example/**") - assertTrue(keyExprA2.intersects(keyExprC)) - } - - @Test - fun includesTest() { - val keyExpr = KeyExpr.tryFrom("example/**") - val includedKeyExpr = KeyExpr.tryFrom("example/A/B/C/D") - assertTrue(keyExpr.includes(includedKeyExpr)) - - val notIncludedKeyExpr = KeyExpr.tryFrom("C/D") - assertFalse(keyExpr.includes(notIncludedKeyExpr)) - } - - @Test - fun sessionDeclarationTest() { - val session = Session.open() - val keyExpr = session.declareKeyExpr("a/b/c").res() - assertEquals("a/b/c", keyExpr.toString()) - session.close() - keyExpr.close() - } - - @Test - fun sessionUnDeclarationTest() { - val session = Session.open() - val keyExpr = session.declareKeyExpr("a/b/c").res() - assertEquals("a/b/c", keyExpr.toString()) - - session.undeclare(keyExpr).res() // Should not throw exception - - // Undeclaring a key expr that was not declared through a session. - val keyExpr2 = "x/y/z".intoKeyExpr() - assertThrows(SessionException::class.java) {session.undeclare(keyExpr2).res()} - - session.close() - keyExpr.close() - keyExpr2.close() - } - - @Test - fun keyExprIsValidAfterClosingSession() { - val session = Session.open() - val keyExpr = session.declareKeyExpr("a/b/c").res() - session.close() - - assertTrue(keyExpr.isValid()) - assertFalse(keyExpr.toString().isEmpty()) // An operation such as toString that goes through JNI is still valid. - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/PublisherTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/PublisherTest.kt deleted file mode 100644 index ee5786a9..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/PublisherTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.Encoding -import io.zenoh.prelude.SampleKind -import io.zenoh.publication.Publisher -import io.zenoh.sample.Sample -import io.zenoh.subscriber.Subscriber -import io.zenoh.value.Value -import kotlin.test.* - -class PublisherTest { - - lateinit var session: Session - lateinit var receivedSamples: ArrayList - lateinit var publisher: Publisher - lateinit var subscriber: Subscriber - lateinit var keyExpr: KeyExpr - - @BeforeTest - fun setUp() { - session = Session.open() - keyExpr = "example/testing/keyexpr".intoKeyExpr() - publisher = session.declarePublisher(keyExpr).res() - subscriber = session.declareSubscriber(keyExpr).with { sample -> - receivedSamples.add(sample) - }.res() - receivedSamples = ArrayList() - } - - @AfterTest - fun tearDown() { - publisher.close() - subscriber.close() - session.close() - keyExpr.close() - } - - @Test - fun putTest() { - - val testValues = arrayListOf( - Value("Test 1".encodeToByteArray(), Encoding(Encoding.ID.TEXT_PLAIN)), - Value("Test 2".encodeToByteArray(), Encoding(Encoding.ID.TEXT_JSON)), - Value("Test 3".encodeToByteArray(), Encoding(Encoding.ID.TEXT_CSV)) - ) - - testValues.forEach() { value -> publisher.put(value).res() } - - assertEquals(receivedSamples.size, testValues.size) - for ((index, sample) in receivedSamples.withIndex()) { - assertEquals(sample.value, testValues[index]) - } - } - - @Test - fun deleteTest() { - publisher.delete().res() - assertEquals(1, receivedSamples.size) - assertEquals(SampleKind.DELETE, receivedSamples[0].kind) - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/PutTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/PutTest.kt deleted file mode 100644 index 2c34d2e9..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/PutTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.Encoding -import io.zenoh.sample.Sample -import io.zenoh.value.Value -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class PutTest { - - companion object { - const val TEST_KEY_EXP = "example/testing/keyexpr" - const val TEST_PAYLOAD = "Hello" - } - - @Test - fun putTest() { - val session = Session.open() - var receivedSample: Sample? = null - val keyExpr = TEST_KEY_EXP.intoKeyExpr() - val subscriber = session.declareSubscriber(keyExpr).with { sample -> receivedSample = sample }.res() - val value = Value(TEST_PAYLOAD.toByteArray(), Encoding(Encoding.ID.TEXT_PLAIN)) - session.put(keyExpr, value).res() - subscriber.close() - session.close() - keyExpr.close() - assertNotNull(receivedSample) - assertEquals(value, receivedSample!!.value) - } -} - diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/QueryableTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/QueryableTest.kt deleted file mode 100644 index f4f724a3..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/QueryableTest.kt +++ /dev/null @@ -1,241 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.handlers.Handler -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Priority -import io.zenoh.prelude.QoS -import io.zenoh.prelude.SampleKind -import io.zenoh.query.Reply -import io.zenoh.queryable.Query -import io.zenoh.sample.Sample -import io.zenoh.value.Value -import org.apache.commons.net.ntp.TimeStamp -import java.time.Duration -import java.time.Instant -import java.util.* -import java.util.concurrent.BlockingQueue -import kotlin.test.* - -class QueryableTest { - - companion object { - val TEST_KEY_EXP = "example/testing/keyexpr".intoKeyExpr() - const val testPayload = "Hello queryable" - } - - private lateinit var session: Session - private lateinit var testKeyExpr: KeyExpr - - @BeforeTest - fun setUp() { - session = Session.open() - testKeyExpr = "example/testing/keyexpr".intoKeyExpr() - } - - @AfterTest - fun tearDown() { - session.close() - testKeyExpr.close() - } - - /** Test validating both Queryable and get operations. */ - @Test - fun queryable_runsWithCallback() { - val sample = Sample( - testKeyExpr, Value(testPayload), SampleKind.PUT, TimeStamp(Date.from(Instant.now())), QoS.default() - ) - val queryable = session.declareQueryable(testKeyExpr).with { query -> - query.reply(testKeyExpr).success(sample.value).timestamp(sample.timestamp!!).res() - }.res() - - var reply: Reply? = null - val delay = Duration.ofMillis(1000) - session.get(testKeyExpr).with { reply = it }.timeout(delay).res() - - Thread.sleep(1000) - - assertTrue(reply is Reply.Success) - assertEquals((reply as Reply.Success).sample, sample) - - queryable.close() - } - - @Test - fun queryable_runsWithHandler() { - val handler = QueryHandler() - val queryable = session.declareQueryable(testKeyExpr).with(handler).res() - - val receivedReplies = ArrayList() - session.get(testKeyExpr).with { reply: Reply -> - receivedReplies.add(reply) - }.res() - - Thread.sleep(500) - - queryable.close() - assertTrue(receivedReplies.all { it is Reply.Success }) - assertEquals(handler.performedReplies.size, receivedReplies.size) - } - - @Test - fun queryableBuilder_queueHandlerIsTheDefaultHandler() { - val queryable = session.declareQueryable(TEST_KEY_EXP).res() - assertTrue(queryable.receiver is BlockingQueue>) - queryable.close() - } - - @Test - fun queryTest() { - var receivedQuery: Query? = null - val queryable = session.declareQueryable(testKeyExpr).with { query -> receivedQuery = query }.res() - - session.get(testKeyExpr).res() - - Thread.sleep(1000) - queryable.close() - assertNotNull(receivedQuery) - assertNull(receivedQuery!!.value) - } - - @Test - fun queryWithValueTest() { - var receivedQuery: Query? = null - val queryable = session.declareQueryable(testKeyExpr).with { query -> receivedQuery = query }.res() - - session.get(testKeyExpr).withValue("Test value").res() - - Thread.sleep(1000) - queryable.close() - assertNotNull(receivedQuery) - assertEquals(Value("Test value"), receivedQuery!!.value) - } - - @Test - fun queryReplySuccessTest() { - val message = "Test message" - val timestamp = TimeStamp.getCurrentTime() - val priority = Priority.DATA_HIGH - val express = true - val congestionControl = CongestionControl.DROP - val queryable = session.declareQueryable(testKeyExpr).with { - it.use { query -> - query.reply(testKeyExpr).success(message).timestamp(timestamp).priority(priority).express(express) - .congestionControl(congestionControl).res() - } - }.res() - - var receivedReply: Reply? = null - session.get(testKeyExpr).with { receivedReply = it }.timeout(Duration.ofMillis(10)).res() - - queryable.close() - - assertTrue(receivedReply is Reply.Success) - val reply = receivedReply as Reply.Success - assertEquals(message, reply.sample.value.payload.decodeToString()) - assertEquals(timestamp, reply.sample.timestamp) - assertEquals(priority, reply.sample.qos.priority) - assertEquals(express, reply.sample.qos.express) - assertEquals(congestionControl, reply.sample.qos.congestionControl) - } - - @Test - fun queryReplyErrorTest() { - val message = "Error message" - val queryable = session.declareQueryable(testKeyExpr).with { - it.use { query -> - query.reply(testKeyExpr).error(Value(message)).res() - } - }.res() - - var receivedReply: Reply? = null - session.get(testKeyExpr).with { receivedReply = it }.timeout(Duration.ofMillis(10)).res() - - Thread.sleep(1000) - queryable.close() - - assertNotNull(receivedReply) - assertTrue(receivedReply is Reply.Error) - val reply = receivedReply as Reply.Error - assertEquals(message, reply.error.payload.decodeToString()) - } - - @Test - fun queryReplyDeleteTest() { - val timestamp = TimeStamp.getCurrentTime() - val priority = Priority.DATA_HIGH - val express = true - val congestionControl = CongestionControl.DROP - val queryable = session.declareQueryable(testKeyExpr).with { - it.use { query -> - query.reply(testKeyExpr).delete().timestamp(timestamp).priority(priority).express(express) - .congestionControl(congestionControl).res() - } - }.res() - - var receivedReply: Reply? = null - session.get(testKeyExpr).with { receivedReply = it }.timeout(Duration.ofMillis(10)).res() - - queryable.close() - - assertNotNull(receivedReply) - assertTrue(receivedReply is Reply.Delete) - val reply = receivedReply as Reply.Delete - assertEquals(timestamp, reply.timestamp) - assertEquals(priority, reply.qos.priority) - assertEquals(express, reply.qos.express) - assertEquals(congestionControl, reply.qos.congestionControl) - } - - @Test - fun onCloseTest() { - var onCloseWasCalled = false - val queryable = session.declareQueryable(testKeyExpr).onClose { onCloseWasCalled = true }.res() - queryable.undeclare() - - assertTrue(onCloseWasCalled) - } -} - -/** A dummy handler that replies "Hello queryable" followed by the count of replies performed. */ -private class QueryHandler : Handler { - - private var counter = 0 - - val performedReplies: ArrayList = ArrayList() - - override fun handle(t: Query) { - reply(t) - } - - override fun receiver(): QueryHandler { - return this - } - - override fun onClose() {} - - fun reply(query: Query) { - val payload = "Hello queryable $counter!" - counter++ - val sample = Sample( - query.keyExpr, Value(payload), SampleKind.PUT, TimeStamp(Date.from(Instant.now())), QoS.default() - ) - performedReplies.add(sample) - query.reply(query.keyExpr).success(sample.value).timestamp(sample.timestamp!!).res() - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/SelectorTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/SelectorTest.kt deleted file mode 100644 index 99d133f4..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/SelectorTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package io.zenoh - -import io.zenoh.exceptions.KeyExprException -import io.zenoh.selector.Selector -import io.zenoh.selector.intoSelector -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -class SelectorTest { - - init { - Zenoh.load() - } - - @Test - fun selectorFromStringTest() { - "a/b/c?arg1=val1".intoSelector().use { selector: Selector -> - assertEquals("a/b/c", selector.keyExpr.toString()) - assertEquals("arg1=val1", selector.parameters) - } - - "a/b/c".intoSelector().use { selector: Selector -> - assertEquals("a/b/c", selector.keyExpr.toString()) - assertEquals("", selector.parameters) - } - - assertFailsWith { "".intoSelector() } - } -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/SessionTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/SessionTest.kt deleted file mode 100644 index 89d1a47b..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/SessionTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.exceptions.SessionException -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.sample.Sample -import kotlin.test.* - -class SessionTest { - - companion object { - val TEST_KEY_EXP = "example/testing/keyexpr".intoKeyExpr() - } - - @Test - fun sessionStartCloseTest() { - val session = Session.open() - assertTrue(session.isOpen()) - session.close() - assertFalse(session.isOpen()) - } - - @Test - fun sessionStop_stopUnopenedSessionIsNoOp() { - val session = Session.open() - session.close() - } - - @Test - fun sessionClose_doesNotThrowExceptionWhenStoppingSessionWithActiveDeclarations() { - val session = Session.open() - session.declarePublisher(TEST_KEY_EXP) - session.close() - } - - @Test - fun sessionDeclare_sessionIsOpenFromInitialization() { - val session = Session.open() - assertTrue(session.isOpen()) - session.close() - } - - @Test - fun sessionClose_newDeclarationsReturnNullAfterClosingSession() { - val session = Session.open() - session.close() - assertFailsWith { session.declarePublisher(TEST_KEY_EXP).res() } - assertFailsWith { session.declareSubscriber(TEST_KEY_EXP).with {}.res() } - assertFailsWith { session.declareQueryable(TEST_KEY_EXP).with {}.res() } - } - -} diff --git a/zenoh-java/src/commonTest/kotlin/io/zenoh/SubscriberTest.kt b/zenoh-java/src/commonTest/kotlin/io/zenoh/SubscriberTest.kt deleted file mode 100644 index 051faaab..00000000 --- a/zenoh-java/src/commonTest/kotlin/io/zenoh/SubscriberTest.kt +++ /dev/null @@ -1,150 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// - -package io.zenoh - -import io.zenoh.handlers.Handler -import io.zenoh.keyexpr.KeyExpr -import io.zenoh.keyexpr.intoKeyExpr -import io.zenoh.prelude.CongestionControl -import io.zenoh.prelude.Encoding -import io.zenoh.prelude.Priority -import io.zenoh.sample.Sample -import io.zenoh.value.Value -import java.util.* -import java.util.concurrent.BlockingQueue -import kotlin.collections.ArrayDeque -import kotlin.collections.ArrayList -import kotlin.test.* - -class SubscriberTest { - - companion object { - val TEST_PRIORITY = Priority.DATA_HIGH; - val TEST_CONGESTION_CONTROL = CongestionControl.BLOCK; - - val testValues = arrayListOf( - Value("Test 1".encodeToByteArray(), Encoding(Encoding.ID.TEXT_PLAIN)), - Value("Test 2".encodeToByteArray(), Encoding(Encoding.ID.TEXT_JSON)), - Value("Test 3".encodeToByteArray(), Encoding(Encoding.ID.TEXT_CSV)) - ) - } - - private lateinit var session: Session - private lateinit var testKeyExpr: KeyExpr - - @BeforeTest - fun setUp() { - session = Session.open() - testKeyExpr = "example/testing/keyexpr".intoKeyExpr() - } - - @AfterTest - fun tearDown() { - session.close() - testKeyExpr.close() - } - - @Test - fun subscriber_runsWithCallback() { - val receivedSamples = ArrayList() - val subscriber = - session.declareSubscriber(testKeyExpr).with { sample -> receivedSamples.add(sample) }.res() - - testValues.forEach { value -> - session.put(testKeyExpr, value) - .priority(TEST_PRIORITY) - .congestionControl(TEST_CONGESTION_CONTROL) - .res() - } - assertEquals(receivedSamples.size, testValues.size) - - receivedSamples.zip(testValues).forEach { (sample, value) -> - assertEquals(sample.value, value) - assertEquals(sample.qos.priority(), TEST_PRIORITY) - assertEquals(sample.qos.congestionControl(), TEST_CONGESTION_CONTROL) - } - - subscriber.close() - } - - @Test - fun subscriber_runsWithHandler() { - val handler = QueueHandler() - val subscriber = session.declareSubscriber(testKeyExpr).with(handler).res() - - testValues.forEach { value -> - session.put(testKeyExpr, value) - .priority(TEST_PRIORITY) - .congestionControl(TEST_CONGESTION_CONTROL) - .res() - } - assertEquals(handler.queue.size, testValues.size) - - handler.queue.zip(testValues).forEach { (sample, value) -> - assertEquals(sample.value, value) - assertEquals(sample.qos.priority(), TEST_PRIORITY) - assertEquals(sample.qos.congestionControl(), TEST_CONGESTION_CONTROL) - } - - subscriber.close() - } - - @Test - fun subscriberBuilder_queueHandlerIsTheDefaultHandler() { - val subscriber = session.declareSubscriber(testKeyExpr).res() - subscriber.close() - assertTrue(subscriber.receiver is BlockingQueue>) - } - - @Test - fun subscriber_isDeclaredWithNonDeclaredKeyExpression() { - // Declaring a subscriber with an undeclared key expression and verifying it properly receives samples. - val keyExpr = KeyExpr("example/**") - val session = Session.open() - - val receivedSamples = ArrayList() - val subscriber = session.declareSubscriber(keyExpr).with { sample -> receivedSamples.add(sample) }.res() - testValues.forEach { value -> session.put(testKeyExpr, value).res() } - subscriber.close() - - assertEquals(receivedSamples.size, testValues.size) - - for ((index, sample) in receivedSamples.withIndex()) { - assertEquals(sample.value, testValues[index]) - } - } - - @Test - fun onCloseTest() { - var onCloseWasCalled = false - val subscriber = session.declareSubscriber(testKeyExpr).onClose { onCloseWasCalled = true }.res() - subscriber.undeclare() - assertTrue(onCloseWasCalled) - } -} - -private class QueueHandler : Handler> { - - val queue: ArrayDeque = ArrayDeque() - override fun handle(t: T) { - queue.add(t) - } - - override fun receiver(): ArrayDeque { - return queue - } - - override fun onClose() {} -} diff --git a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt index d6b7dd9b..11ae638c 100644 --- a/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-java/src/jvmMain/kotlin/io/zenoh/Zenoh.kt @@ -15,155 +15,140 @@ package io.zenoh import java.io.File +import java.io.FileInputStream import java.io.FileOutputStream import java.io.InputStream -import java.io.FileInputStream import java.util.zip.ZipInputStream /** * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the * log level configuration. */ -internal actual class Zenoh private actual constructor() { - - actual companion object { - private const val ZENOH_LIB_NAME = "zenoh_jni" - private const val ZENOH_LOGS_PROPERTY = "zenoh.logger" - - private var instance: Zenoh? = null +internal actual object ZenohLoad { + private const val ZENOH_LIB_NAME = "zenoh_jni" - actual fun load() { - instance ?: Zenoh().also { instance = it } + init { + // Try first to load the local native library for cases in which the module was built locally, + // otherwise try to load from the JAR. + if (tryLoadingLocalLibrary().isFailure) { + val target = determineTarget().getOrThrow() + tryLoadingLibraryFromJarPackage(target).getOrThrow() } + } - /** - * Determine target - * - * Determines the [Target] corresponding to the machine on top of which the native code will run. - * - * @return A result with the target. - */ - private fun determineTarget(): Result = runCatching { - val osName = System.getProperty("os.name").lowercase() - val osArch = System.getProperty("os.arch") - - val target = when { - osName.contains("win") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.WINDOWS_X86_64_MSVC - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - osName.contains("mac") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.APPLE_X86_64 - osArch.contains("aarch64") -> Target.APPLE_AARCH64 - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.LINUX_X86_64 - osArch.contains("aarch64") -> Target.LINUX_AARCH64 - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - else -> throw UnsupportedOperationException("Unsupported platform: $osName") + /** + * Determine target + * + * Determines the [Target] corresponding to the machine on top of which the native code will run. + * + * @return A result with the target. + */ + private fun determineTarget(): Result = runCatching { + val osName = System.getProperty("os.name").lowercase() + val osArch = System.getProperty("os.arch") + + val target = when { + osName.contains("win") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.WINDOWS_X86_64_MSVC + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - return Result.success(target) - } - /** - * Unzip library. - * - * The Zenoh libraries are stored within the JAR as compressed ZIP files. - * The location of the zipped files is expected to be under target/target.zip. - * It is expected that the zip file only contains the compressed library. - * - * The uncompressed library will be stored temporarily and deleted on exit. - * - * @param compressedLib Input stream pointing to the compressed library. - * @return A result with the uncompressed library file. - */ - private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { - val zipInputStream = ZipInputStream(compressedLib) - val buffer = ByteArray(1024) - val zipEntry = zipInputStream.nextEntry - - val library = File.createTempFile(zipEntry!!.name, ".tmp") - library.deleteOnExit() - - val parent = library.parentFile - if (!parent.exists()) { - parent.mkdirs() + osName.contains("mac") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.APPLE_X86_64 + osArch.contains("aarch64") -> Target.APPLE_AARCH64 + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - val fileOutputStream = FileOutputStream(library) - var len: Int - while (zipInputStream.read(buffer).also { len = it } > 0) { - fileOutputStream.write(buffer, 0, len) + osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.LINUX_X86_64 + osArch.contains("aarch64") -> Target.LINUX_AARCH64 + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - fileOutputStream.close() - zipInputStream.closeEntry() - zipInputStream.close() - return Result.success(library) + else -> throw UnsupportedOperationException("Unsupported platform: $osName") } + return Result.success(target) + } - private fun loadLibraryAsInputStream(target: Target): Result = runCatching { - val libUrl = ClassLoader.getSystemClassLoader().getResourceAsStream("$target/$target.zip")!! - val uncompressedLibFile = unzipLibrary(libUrl) - return Result.success(FileInputStream(uncompressedLibFile.getOrThrow())) + /** + * Unzip library. + * + * The Zenoh libraries are stored within the JAR as compressed ZIP files. + * The location of the zipped files is expected to be under target/target.zip. + * It is expected that the zip file only contains the compressed library. + * + * The uncompressed library will be stored temporarily and deleted on exit. + * + * @param compressedLib Input stream pointing to the compressed library. + * @return A result with the uncompressed library file. + */ + private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { + val zipInputStream = ZipInputStream(compressedLib) + val buffer = ByteArray(1024) + val zipEntry = zipInputStream.nextEntry + + val library = File.createTempFile(zipEntry!!.name, ".tmp") + library.deleteOnExit() + + val parent = library.parentFile + if (!parent.exists()) { + parent.mkdirs() } - @Suppress("UnsafeDynamicallyLoadedCode") - private fun loadZenohJNI(inputStream: InputStream) { - val tempLib = File.createTempFile("tempLib", ".tmp") - tempLib.deleteOnExit() + val fileOutputStream = FileOutputStream(library) + var len: Int + while (zipInputStream.read(buffer).also { len = it } > 0) { + fileOutputStream.write(buffer, 0, len) + } + fileOutputStream.close() - FileOutputStream(tempLib).use { output -> - inputStream.copyTo(output) - } + zipInputStream.closeEntry() + zipInputStream.close() + return Result.success(library) + } - System.load(tempLib.absolutePath) - } + private fun loadLibraryAsInputStream(target: Target): Result = runCatching { + val libUrl = ClassLoader.getSystemClassLoader().getResourceAsStream("$target/$target.zip")!! + val uncompressedLibFile = unzipLibrary(libUrl) + return Result.success(FileInputStream(uncompressedLibFile.getOrThrow())) + } - /** - * Load library from jar package. - * - * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. - * - * @param target - */ - private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { - val lib: Result = loadLibraryAsInputStream(target) - lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } - } + @Suppress("UnsafeDynamicallyLoadedCode") + private fun loadZenohJNI(inputStream: InputStream) { + val tempLib = File.createTempFile("tempLib", ".tmp") + tempLib.deleteOnExit() - /** - * Try loading local library. - * - * This function aims to load the default library that is usually included when building the zenoh kotlin library - * locally. - */ - private fun tryLoadingLocalLibrary(): Result = runCatching { - val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) - if (lib != null) { - loadZenohJNI(lib) - } else { - throw Exception("Unable to load local Zenoh JNI.") - } + FileOutputStream(tempLib).use { output -> + inputStream.copyTo(output) } + + System.load(tempLib.absolutePath) } - init { - // Try first to load the local native library for cases in which the module was built locally, - // otherwise try to load from the JAR. - if (tryLoadingLocalLibrary().isFailure) { - val target = determineTarget().getOrThrow() - tryLoadingLibraryFromJarPackage(target).getOrThrow() - } + /** + * Load library from jar package. + * + * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. + * + * @param target + */ + private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { + val lib: Result = loadLibraryAsInputStream(target) + lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } + } - val logLevel = System.getProperty(ZENOH_LOGS_PROPERTY) - if (logLevel != null) { - Logger.start(logLevel) + /** + * Try loading local library. + * + * This function aims to load the default library that is usually included when building the zenoh kotlin library + * locally. + */ + private fun tryLoadingLocalLibrary(): Result = runCatching { + val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) + if (lib != null) { + loadZenohJNI(lib) + } else { + throw Exception("Unable to load local Zenoh JNI.") } } } diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/ConfigTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/ConfigTest.java new file mode 100644 index 00000000..990c5452 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/ConfigTest.java @@ -0,0 +1,352 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class ConfigTest { + + private static final String json5ClientConfigString = + "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " },\n" + + " scouting: {\n" + + " multicast: {\n" + + " enabled: false\n" + + " }\n" + + " }\n" + + "}"; + + private static final String json5ServerConfigString = + "{\n" + + " mode: \"peer\",\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7450\"],\n" + + " },\n" + + " scouting: {\n" + + " multicast: {\n" + + " enabled: false,\n" + + " }\n" + + " }\n" + + "}"; + + private static final String jsonClientConfigString = + "{\n" + + " \"mode\": \"peer\",\n" + + " \"connect\": {\n" + + " \"endpoints\": [\"tcp/localhost:7450\"]\n" + + " },\n" + + " \"scouting\": {\n" + + " \"multicast\": {\n" + + " \"enabled\": false\n" + + " }\n" + + " }\n" + + "}"; + + private static final String jsonServerConfigString = + "{\n" + + " \"mode\": \"peer\",\n" + + " \"listen\": {\n" + + " \"endpoints\": [\"tcp/localhost:7450\"]\n" + + " },\n" + + " \"scouting\": {\n" + + " \"multicast\": {\n" + + " \"enabled\": false\n" + + " }\n" + + " }\n" + + "}"; + + private static final String yamlClientConfigString = + "mode: peer\n" + + "connect:\n" + + " endpoints:\n" + + " - tcp/localhost:7450\n" + + "scouting:\n" + + " multicast:\n" + + " enabled: false\n"; + + private static final String yamlServerConfigString = + "mode: peer\n" + + "listen:\n" + + " endpoints:\n" + + " - tcp/localhost:7450\n" + + "scouting:\n" + + " multicast:\n" + + " enabled: false\n"; + + private static final KeyExpr TEST_KEY_EXP; + private static final Config json5ClientConfig; + private static final Config json5ServerConfig; + private static final Config jsonClientConfig; + private static final Config jsonServerConfig; + private static final Config yamlClientConfig; + private static final Config yamlServerConfig; + + static { + try { + TEST_KEY_EXP = KeyExpr.tryFrom("example/testing/keyexpr"); + json5ClientConfig = Config.fromJson5(json5ClientConfigString); + json5ServerConfig = Config.fromJson5(json5ServerConfigString); + + jsonClientConfig = Config.fromJson(jsonClientConfigString); + jsonServerConfig = Config.fromJson(jsonServerConfigString); + + yamlClientConfig = Config.fromYaml(yamlClientConfigString); + yamlServerConfig = Config.fromYaml(yamlServerConfigString); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + private void runSessionTest(Config clientConfig, Config serverConfig) throws ZError, InterruptedException { + Session sessionClient = Zenoh.open(clientConfig); + Session sessionServer = Zenoh.open(serverConfig); + + final Sample[] receivedSample = new Sample[1]; + + Subscriber subscriber = + sessionClient.declareSubscriber(TEST_KEY_EXP, sample -> receivedSample[0] = sample); + ZBytes payload = ZBytes.from("example message"); + sessionClient.put(TEST_KEY_EXP, payload); + + Thread.sleep(1000); + + subscriber.close(); + sessionClient.close(); + sessionServer.close(); + + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getPayload(), payload); + } + + @Test + public void testConfigWithJSON5() throws ZError, InterruptedException { + runSessionTest(json5ClientConfig, json5ServerConfig); + } + + @Test + public void testConfigLoadsFromJSONString() throws ZError, InterruptedException { + runSessionTest(jsonClientConfig, jsonServerConfig); + } + + + @Test + public void testConfigLoadsFromYAMLString() throws ZError, InterruptedException { + runSessionTest(yamlClientConfig, yamlServerConfig); + } + + @Test + public void testDefaultConfig() throws ZError { + Config config = Config.loadDefault(); + Session session = Zenoh.open(config); + session.close(); + } + + @Test + public void configFailsWithIllFormatedJsonTest() throws ZError { + String illFormattedConfig = + "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"],\n" + + // missing '}' character here + "}"; + + assertThrows(ZError.class, () -> Config.fromJson(illFormattedConfig)); + } + + @Test + public void configFailsWithIllFormatedYAMLTest() { + String illFormattedConfig = + "mode: peer\n" + + "connect:\n" + + " endpoints:\n" + + " - tcp/localhost:7450\n" + + "scouting\n"; + + assertThrows(ZError.class, () -> Config.fromJson(illFormattedConfig)); + } + + @Test + public void configLoadsFromJSONFileTest() throws IOException, ZError, InterruptedException { + File clientConfigFile = File.createTempFile("clientConfig", ".json"); + File serverConfigFile = File.createTempFile("serverConfig", ".json"); + + try { + // Writing text to the files + Files.write(clientConfigFile.toPath(), jsonClientConfigString.getBytes()); + Files.write(serverConfigFile.toPath(), jsonServerConfigString.getBytes()); + + Config loadedClientConfig = Config.fromFile(clientConfigFile); + Config loadedServerConfig = Config.fromFile(serverConfigFile); + + runSessionTest(loadedClientConfig, loadedServerConfig); + } finally { + clientConfigFile.delete(); + serverConfigFile.delete(); + } + } + + @Test + public void configLoadsFromYAMLFileTest() throws IOException, ZError, InterruptedException { + File clientConfigFile = File.createTempFile("clientConfig", ".yaml"); + File serverConfigFile = File.createTempFile("serverConfig", ".yaml"); + + try { + // Writing text to the files + Files.write(clientConfigFile.toPath(), yamlClientConfigString.getBytes()); + Files.write(serverConfigFile.toPath(), yamlServerConfigString.getBytes()); + + Config loadedClientConfig = Config.fromFile(clientConfigFile); + Config loadedServerConfig = Config.fromFile(serverConfigFile); + + runSessionTest(loadedClientConfig, loadedServerConfig); + } finally { + clientConfigFile.delete(); + serverConfigFile.delete(); + } + } + + @Test + public void configLoadsFromJSON5FileTest() throws IOException, ZError, InterruptedException { + File clientConfigFile = File.createTempFile("clientConfig", ".json5"); + File serverConfigFile = File.createTempFile("serverConfig", ".json5"); + + try { + // Writing text to the files + Files.write(clientConfigFile.toPath(), json5ClientConfigString.getBytes()); + Files.write(serverConfigFile.toPath(), json5ServerConfigString.getBytes()); + + Config loadedClientConfig = Config.fromFile(clientConfigFile); + Config loadedServerConfig = Config.fromFile(serverConfigFile); + + runSessionTest(loadedClientConfig, loadedServerConfig); + } finally { + clientConfigFile.delete(); + serverConfigFile.delete(); + } + } + + @Test + public void configLoadsFromJSON5FileProvidingPathTest() throws IOException, ZError, InterruptedException { + File clientConfigFile = File.createTempFile("clientConfig", ".json5"); + File serverConfigFile = File.createTempFile("serverConfig", ".json5"); + + try { + // Writing text to the files + Files.write(clientConfigFile.toPath(), json5ClientConfigString.getBytes()); + Files.write(serverConfigFile.toPath(), json5ServerConfigString.getBytes()); + + Config loadedClientConfig = Config.fromFile(clientConfigFile.toPath()); + Config loadedServerConfig = Config.fromFile(serverConfigFile.toPath()); + + runSessionTest(loadedClientConfig, loadedServerConfig); + } finally { + clientConfigFile.delete(); + serverConfigFile.delete(); + } + } + + @Test + public void getJsonFunctionTest() throws ZError { + String jsonConfig = + "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"],\n" + + " },\n" + + " scouting: {\n" + + " multicast: {\n" + + " enabled: false,\n" + + " }\n" + + " }\n" + + "}"; + + Config config = Config.fromJson(jsonConfig); + + String value = config.getJson("connect"); + assertTrue(value.contains("\"endpoints\":[\"tcp/localhost:7450\"]")); + + String value2 = config.getJson("mode"); + assertEquals("\"peer\"", value2); + } + + @Test + public void configShouldRemainValidDespiteFailingToGetJsonValue() throws ZError { + String jsonConfig = + "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"],\n" + + " },\n" + + " scouting: {\n" + + " multicast: {\n" + + " enabled: false,\n" + + " }\n" + + " }\n" + + "}"; + + Config config = Config.fromJson(jsonConfig); + + assertThrows(ZError.class, () -> { + config.getJson("non_existent_key"); + }); + + String mode = config.getJson("mode"); + assertEquals("\"peer\"", mode); + } + + @Test + public void insertJson5FunctionTest() throws ZError { + Config config = Config.loadDefault(); + String endpoints = "[\"tcp/8.8.8.8:8\", \"tcp/8.8.8.8:9\"]"; + + config.insertJson5("listen/endpoints", endpoints); + + String jsonValue = config.getJson("listen/endpoints"); + assertTrue(jsonValue.contains("8.8.8.8")); + } + + @Test + public void insertIllFormattedJson5ShouldFailTest() throws ZError { + Config config = Config.loadDefault(); + + String illFormattedEndpoints = "[\"tcp/8.8.8.8:8\""; + assertThrows(ZError.class, () -> { + config.insertJson5("listen/endpoints", illFormattedEndpoints); + }); + + String correctEndpoints = "[\"tcp/8.8.8.8:8\", \"tcp/8.8.8.8:9\"]"; + config.insertJson5("listen/endpoints", correctEndpoints); + String retrievedEndpoints = config.getJson("listen/endpoints"); + + assertTrue(retrievedEndpoints.contains("8.8.8.8")); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/DeleteTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/DeleteTest.java new file mode 100644 index 00000000..6b31ae6f --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/DeleteTest.java @@ -0,0 +1,47 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.sample.SampleKind; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class DeleteTest { + + @Test + public void deleteIsProperlyReceivedBySubscriberTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + final Sample[] receivedSample = new Sample[1]; + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + session.delete(keyExpr); + + Thread.sleep(1000); + subscriber.close(); + session.close(); + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getKind(), SampleKind.DELETE); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/EncodingTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/EncodingTest.java new file mode 100644 index 00000000..a524672b --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/EncodingTest.java @@ -0,0 +1,217 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.query.*; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class EncodingTest { + + private static final Encoding without_schema = Encoding.TEXT_CSV; + private static final Encoding with_schema = Encoding.APPLICATION_JSON.withSchema("test_schema"); + private ZBytes payload = ZBytes.from("test"); + + @Test + public void encoding_subscriberTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + + // Testing non null schema + Sample[] receivedSample = new Sample[1]; + + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var putOptions = new PutOptions(); + putOptions.setEncoding(with_schema); + session.put(keyExpr, payload, putOptions); + Thread.sleep(200); + + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getEncoding(), with_schema); + + // Testing null schema + receivedSample[0] = null; + putOptions.setEncoding(without_schema); + session.put(keyExpr, payload, putOptions); + Thread.sleep(200); + + assertEquals(receivedSample[0].getEncoding(), without_schema); + + subscriber.close(); + session.close(); + } + + @Test + public void encoding_replySuccessTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/**"); + Selector test1 = Selector.tryFrom("example/testing/reply_success"); + Selector test2 = Selector.tryFrom("example/testing/reply_success_with_schema"); + + var queryable = session.declareQueryable(keyExpr, query -> + { + try { + KeyExpr queryKeyExpr = query.getKeyExpr(); + var options = new ReplyOptions(); + if (queryKeyExpr.equals(test1.getKeyExpr())) { + options.setEncoding(without_schema); + query.reply(queryKeyExpr, payload, options); + } else if (queryKeyExpr.equals(test2.getKeyExpr())) { + options.setEncoding(with_schema); + query.reply(queryKeyExpr, payload, options); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + ); + + // Testing with null schema on a reply success scenario. + Sample[] receivedSample = new Sample[1]; + session.get(test1, reply -> { + assertTrue(reply instanceof Reply.Success); + receivedSample[0] = ((Reply.Success) reply).getSample(); + }); + Thread.sleep(200); + + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getEncoding(), without_schema); + + // Testing with non-null schema on a reply success scenario. + receivedSample[0] = null; + session.get(test2, reply -> { + assertTrue(reply instanceof Reply.Success); + receivedSample[0] = ((Reply.Success) reply).getSample(); + }); + Thread.sleep(200); + + assertNotNull(receivedSample[0]); + assertEquals(receivedSample[0].getEncoding(), with_schema); + + queryable.close(); + session.close(); + } + + @Test + public void encoding_replyErrorTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/**"); + Selector test1 = Selector.tryFrom("example/testing/reply_error"); + Selector test2 = Selector.tryFrom("example/testing/reply_error_with_schema"); + + ZBytes replyPayload = ZBytes.from("test"); + var queryable = session.declareQueryable(keyExpr, query -> + { + KeyExpr keyExpr1 = query.getKeyExpr(); + var options = new ReplyErrOptions(); + try { + if (keyExpr1.equals(test1.getKeyExpr())) { + options.setEncoding(without_schema); + query.replyErr(replyPayload, options); + } else if (keyExpr1.equals(test2.getKeyExpr())) { + options.setEncoding(with_schema); + query.replyErr(replyPayload, options); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + // Testing with null schema on a reply error scenario. + ZBytes[] errorMessage = new ZBytes[1]; + Encoding[] errorEncoding = new Encoding[1]; + session.get(test1, reply -> + { + assertTrue(reply instanceof Reply.Error); + Reply.Error reply1 = (Reply.Error) reply; + errorMessage[0] = reply1.getError(); + errorEncoding[0] = reply1.getEncoding(); + } + ); + Thread.sleep(200); + + assertNotNull(errorMessage[0]); + assertEquals(errorEncoding[0], without_schema); + + Thread.sleep(200); + + // Testing with non-null schema on a reply error scenario. + errorMessage[0] = null; + errorEncoding[0] = null; + session.get(test2, reply -> + { + assertTrue(reply instanceof Reply.Error); + Reply.Error error = (Reply.Error) reply; + errorMessage[0] = error.getError(); + errorEncoding[0] = error.getEncoding(); + }); + Thread.sleep(200); + + assertNotNull(errorMessage[0]); + assertEquals(errorEncoding[0], with_schema); + + queryable.close(); + session.close(); + } + + @Test + public void encoding_queryTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + Selector selector = Selector.tryFrom("example/testing/keyexpr"); + + Encoding[] receivedEncoding = new Encoding[1]; + var queryable = session.declareQueryable(keyExpr, query -> + { + receivedEncoding[0] = query.getEncoding(); + query.close(); + }); + + // Testing with null schema + var getOptions = new GetOptions(); + getOptions.setPayload(payload); + getOptions.setEncoding(without_schema); + session.get(selector, getOptions); + Thread.sleep(200); + + assertEquals(receivedEncoding[0], without_schema); + + Thread.sleep(200); + + // Testing non-null schema + receivedEncoding[0] = null; + getOptions.setEncoding(with_schema); + session.get(selector, getOptions); + Thread.sleep(200); + + assertEquals(receivedEncoding[0], with_schema); + + queryable.close(); + session.close(); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/GetTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/GetTest.java new file mode 100644 index 00000000..b929bb92 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/GetTest.java @@ -0,0 +1,125 @@ +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.handlers.Handler; +import io.zenoh.query.*; +import io.zenoh.sample.Sample; +import io.zenoh.sample.SampleKind; +import org.apache.commons.net.ntp.TimeStamp; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Duration; +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class GetTest { + + static final ZBytes payload = ZBytes.from("test"); + static final TimeStamp timestamp = TimeStamp.getCurrentTime(); + static final SampleKind kind = SampleKind.PUT; + + private Session session; + private Selector selector; + private Queryable queryable; + + @Before + public void setUp() throws ZError { + session = Zenoh.open(Config.loadDefault()); + selector = Selector.tryFrom("example/testing/keyexpr"); + queryable = session.declareQueryable(selector.getKeyExpr(), query -> + { + try { + var options = new ReplyOptions(); + options.setTimeStamp(timestamp); + query.reply(query.getKeyExpr(), payload, options); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + ); + } + + @After + public void tearDown() throws ZError { + session.close(); + selector.close(); + queryable.close(); + } + + @Test + public void get_runsWithCallbackTest() throws ZError { + Reply[] reply = new Reply[1]; + + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + session.get(selector, reply1 -> reply[0] = reply1, getOptions); + + assertNotNull(reply[0]); + Sample sample = ((Reply.Success) reply[0]).getSample(); + assertEquals(payload, sample.getPayload()); + assertEquals(kind, sample.getKind()); + assertEquals(selector.getKeyExpr(), sample.getKeyExpr()); + assertEquals(timestamp, sample.getTimestamp()); + } + + @Test + public void get_runsWithHandlerTest() throws ZError { + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + ArrayList receiver = session.get(selector, new TestHandler(), getOptions); + for (Reply reply : receiver) { + Sample sample = ((Reply.Success) reply).getSample(); + assertEquals(payload, sample.getPayload()); + assertEquals(SampleKind.PUT, sample.getKind()); + } + } + + @Test + public void getWithSelectorParamsTest() throws ZError { + Parameters[] receivedParams = new Parameters[1]; + + Queryable queryable = session.declareQueryable(selector.getKeyExpr(), query -> + receivedParams[0] = query.getParameters() + ); + + Parameters params = Parameters.from("arg1=val1&arg2=val2&arg3"); + Selector selectorWithParams = new Selector(selector.getKeyExpr(), params); + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + session.get(selectorWithParams, getOptions); + + queryable.close(); + + assertEquals(params, receivedParams[0]); + } +} + +/** + * A dummy handler for get operations. + */ +class TestHandler implements Handler> { + + static final ArrayList performedReplies = new ArrayList<>(); + + @Override + public void handle(Reply t) { + performedReplies.add(t); + } + + @Override + public ArrayList receiver() { + return performedReplies; + } + + @Override + public void onClose() { + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/KeyExprTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/KeyExprTest.java new file mode 100644 index 00000000..eca1aba4 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/KeyExprTest.java @@ -0,0 +1,166 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.keyexpr.SetIntersectionLevel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class KeyExprTest { + + @Test + public void creation_TryFromTest() throws ZError { + // A couple of examples of valid and invalid key expressions. + KeyExpr keyExpr = KeyExpr.tryFrom("example/test"); + + assertThrows(ZError.class, () -> KeyExpr.tryFrom("example/test?param='test'")); + + KeyExpr keyExpr3 = KeyExpr.tryFrom("example/*/test"); + + assertThrows(ZError.class, () -> KeyExpr.tryFrom("example/!*/test")); + } + + @Test + public void equalizationTest() throws ZError { + KeyExpr keyExpr1 = KeyExpr.tryFrom("example/test"); + KeyExpr keyExpr2 = KeyExpr.tryFrom("example/test"); + assertEquals(keyExpr1, keyExpr2); + + KeyExpr keyExpr3 = KeyExpr.tryFrom("different/key/expr"); + assertNotEquals(keyExpr1, keyExpr3); + } + + @Test + public void creation_autocanonizeTest() throws ZError { + KeyExpr keyExpr1 = KeyExpr.autocanonize("example/**/test"); + KeyExpr keyExpr2 = KeyExpr.autocanonize("example/**/**/test"); + assertEquals(keyExpr1, keyExpr2); + } + + @Test + public void toStringTest() throws ZError { + String keyExprStr = "example/test/a/b/c"; + KeyExpr keyExpr = KeyExpr.tryFrom(keyExprStr); + assertEquals(keyExprStr, keyExpr.toString()); + assertEquals(keyExprStr, keyExpr.toString()); + } + + @Test + public void intersectionTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("example/*/test"); + + KeyExpr keyExprB = KeyExpr.tryFrom("example/B/test"); + assertTrue(keyExprA.intersects(keyExprB)); + + KeyExpr keyExprC = KeyExpr.tryFrom("example/B/C/test"); + assertFalse(keyExprA.intersects(keyExprC)); + + KeyExpr keyExprA2 = KeyExpr.tryFrom("example/**"); + assertTrue(keyExprA2.intersects(keyExprC)); + } + + @Test + public void includesTest() throws ZError { + KeyExpr keyExpr = KeyExpr.tryFrom("example/**"); + KeyExpr includedKeyExpr = KeyExpr.tryFrom("example/A/B/C/D"); + assertTrue(keyExpr.includes(includedKeyExpr)); + + KeyExpr notIncludedKeyExpr = KeyExpr.tryFrom("C/D"); + assertFalse(keyExpr.includes(notIncludedKeyExpr)); + } + + @Test + public void sessionDeclarationTest() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = session.declareKeyExpr("a/b/c"); + assertEquals("a/b/c", keyExpr.toString()); + session.close(); + keyExpr.close(); + } + + @Test + public void sessionUnDeclarationTest() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + KeyExpr keyExpr = session.declareKeyExpr("a/b/c"); + assertEquals("a/b/c", keyExpr.toString()); + + session.undeclare(keyExpr); + + // Undeclaring twice a key expression shall fail. + assertThrows(ZError.class, () -> session.undeclare(keyExpr)); + + // Undeclaring a key expr that was not declared through a session. + KeyExpr keyExpr2 = KeyExpr.tryFrom("x/y/z"); + assertThrows(ZError.class, () -> session.undeclare(keyExpr2)); + + session.close(); + } + + @Test + public void relationTo_includesTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/**"); + KeyExpr keyExprB = KeyExpr.tryFrom("A/B/C"); + + assertEquals(SetIntersectionLevel.INCLUDES, keyExprA.relationTo(keyExprB)); + } + + @Test + public void relationTo_intersectsTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/*/C/D"); + KeyExpr keyExprB = KeyExpr.tryFrom("A/B/C/*"); + + assertEquals(SetIntersectionLevel.INTERSECTS, keyExprA.relationTo(keyExprB)); + } + + @Test + public void relationTo_equalsTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/B/C"); + KeyExpr keyExprB = KeyExpr.tryFrom("A/B/C"); + + assertEquals(SetIntersectionLevel.EQUALS, keyExprA.relationTo(keyExprB)); + } + + @Test + public void relationTo_disjointTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/B/C"); + KeyExpr keyExprB = KeyExpr.tryFrom("D/E/F"); + + assertEquals(SetIntersectionLevel.DISJOINT, keyExprA.relationTo(keyExprB)); + } + + @Test + public void joinTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/B"); + KeyExpr keyExprExpected = KeyExpr.tryFrom("A/B/C/D"); + + KeyExpr keyExprJoined = keyExprA.join("C/D"); + assertEquals(keyExprExpected, keyExprJoined); + } + + @Test + public void concatTest() throws ZError { + KeyExpr keyExprA = KeyExpr.tryFrom("A/B"); + KeyExpr keyExprExpected = KeyExpr.tryFrom("A/B/C/D"); + + KeyExpr keyExprConcat = keyExprA.concat("/C/D"); + assertEquals(keyExprExpected, keyExprConcat); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/LivelinessTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/LivelinessTest.java new file mode 100644 index 00000000..a43741c6 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/LivelinessTest.java @@ -0,0 +1,70 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.liveliness.LivelinessToken; +import io.zenoh.query.Reply; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class LivelinessTest { + + @Test + public void getLivelinessTest() throws ZError, InterruptedException { + Session sessionA = Zenoh.open(Config.loadDefault()); + Session sessionB = Zenoh.open(Config.loadDefault()); + + var keyExpr = KeyExpr.tryFrom("test/liveliness"); + LivelinessToken token = sessionA.liveliness().declareToken(keyExpr); + + Reply[] receivedReply = new Reply[1]; + sessionB.liveliness().get(KeyExpr.tryFrom("test/**"), reply -> receivedReply[0] = reply); + + Thread.sleep(1000); + + assertNotNull(receivedReply[0]); + token.close(); + sessionA.close(); + sessionB.close(); + } + + @Test + public void livelinessSubscriberTest() throws ZError, InterruptedException { + Session sessionA = Zenoh.open(Config.loadDefault()); + Session sessionB = Zenoh.open(Config.loadDefault()); + + Sample[] receivedSample = new Sample[1]; + + var subscriber = sessionA.liveliness().declareSubscriber(KeyExpr.tryFrom("test/**"), sample -> receivedSample[0] = sample); + + var token = sessionB.liveliness().declareToken(KeyExpr.tryFrom("test/liveliness")); + + Thread.sleep(1000); + + assertNotNull(receivedSample[0]); + + token.close(); + subscriber.close(); + sessionA.close(); + sessionB.close(); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/ParametersTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/ParametersTest.java new file mode 100644 index 00000000..0b6ab63b --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/ParametersTest.java @@ -0,0 +1,125 @@ +package io.zenoh; + +import io.zenoh.query.Parameters; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class ParametersTest { + + @Test + public void shouldCreateEmptyParametersFromEmptyString() { + var result = Parameters.from(""); + + assertTrue(result.isEmpty()); + } + + @Test + public void shouldParseParametersFromFormattedString() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals("1", parameters.get("a")); + assertEquals("2", parameters.get("b")); + assertEquals("3|4|5", parameters.get("c")); + assertEquals("6", parameters.get("d")); + } + + @Test + public void shouldReturnListOfValuesSplitBySeparator() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals(List.of("3", "4", "5"), parameters.values("c")); + } + + @Test + public void containsKeyTest() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertTrue(parameters.containsKey("a")); + assertFalse(parameters.containsKey("e")); + } + + @Test + public void getTest() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals("1", parameters.get("a")); + assertEquals("2", parameters.get("b")); + assertEquals("3|4|5", parameters.get("c")); + assertEquals("6", parameters.get("d")); + } + + @Test + public void getOrDefaultTest() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals("1", parameters.get("a")); + assertEquals("None", parameters.getOrDefault("e", "None")); + } + + @Test + public void toMapTest() { + var parameters = Parameters.from("a=1;b=2;c=3|4|5;d=6"); + + assertEquals(Map.of("a", "1", "b", "2", "c", "3|4|5", "d", "6"), parameters.toMap()); + } + + @Test + public void insertShouldReturnPreviouslyContainedValue() { + var parameters = Parameters.from("a=1"); + var oldValue = parameters.insert("a", "3"); + assertEquals("1", oldValue); + } + + @Test + public void insertShouldReturnNullIfNotAlreadyPresent() { + var parameters = Parameters.empty(); + var oldValue = parameters.insert("a", "1"); + assertNull(oldValue); + } + + @Test + public void removeShouldReturnOldValueIfPresent() { + var parameters = Parameters.from("a=1"); + var oldValue = parameters.remove("a"); + assertEquals("1", oldValue); + } + + @Test + public void removeShouldReturnNullIfNotAlreadyPresent() { + var parameters = Parameters.empty(); + var oldValue = parameters.remove("a"); + assertNull(oldValue); + } + + @Test + public void extendTest() { + var parameters = Parameters.from("a=1;b=2"); + parameters.extend(Parameters.from("c=3;d=4")); + + assertEquals(Parameters.from("a=1;b=2;c=3;d=4"), parameters); + + parameters.extend(Map.of("e", "5")); + assertEquals(Parameters.from("a=1;b=2;c=3;d=4;e=5"), parameters); + } + + @Test + public void extendOverwritesConflictingKeysTest() { + var parameters = Parameters.from("a=1;b=2"); + parameters.extend(Parameters.from("b=3;d=4")); + + assertEquals(Parameters.from("a=1;b=3;d=4"), parameters); + } + + @Test + public void emptyParametersToStringTest() { + var parameters = Parameters.empty(); + assertEquals("", parameters.toString()); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/PublisherTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/PublisherTest.java new file mode 100644 index 00000000..d1e158f1 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/PublisherTest.java @@ -0,0 +1,99 @@ +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.bytes.Encoding; +import io.zenoh.pubsub.PublisherOptions; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.qos.QoS; +import io.zenoh.qos.Reliability; +import io.zenoh.sample.SampleKind; +import io.zenoh.pubsub.Publisher; +import io.zenoh.sample.Sample; +import io.zenoh.pubsub.Subscriber; +import kotlin.Pair; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +@RunWith(JUnit4.class) +public class PublisherTest { + + private Session session; + private ArrayList receivedSamples; + private Publisher publisher; + private Subscriber subscriber; + private KeyExpr keyExpr; + + @Before + public void setUp() throws ZError { + session = Zenoh.open(Config.loadDefault()); + keyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + + var config = new PublisherOptions(); + config.setReliability(Reliability.RELIABLE); + config.setEncoding(Encoding.ZENOH_STRING); + publisher = session.declarePublisher(keyExpr, config); + + receivedSamples = new ArrayList<>(); + + subscriber = session.declareSubscriber(keyExpr, receivedSamples::add); + } + + @After + public void tearDown() throws ZError { + publisher.close(); + subscriber.close(); + session.close(); + keyExpr.close(); + } + + @Test + public void putTest() { + + List> testPayloads = List.of( + new Pair<>(ZBytes.from("Test 1"), Encoding.TEXT_PLAIN), + new Pair<>(ZBytes.from("Test 2"), Encoding.TEXT_JSON), + new Pair<>(ZBytes.from("Test 3"), Encoding.TEXT_CSV) + ); + + testPayloads.forEach(value -> { + try { + var putOptions = new PutOptions(); + putOptions.setEncoding(value.getSecond()); + publisher.put(value.getFirst(), putOptions); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + assertEquals(testPayloads.size(), receivedSamples.size()); + for (int index = 0; index < receivedSamples.size(); index++) { + var sample = receivedSamples.get(index); + assertEquals(testPayloads.get(index).getFirst(), sample.getPayload()); + assertEquals(testPayloads.get(index).getSecond(), sample.getEncoding()); + } + } + + @Test + public void deleteTest() throws ZError { + publisher.delete(); + assertEquals(1, receivedSamples.size()); + assertEquals(SampleKind.DELETE, receivedSamples.get(0).getKind()); + } + + @Test + public void shouldFallbackToPublisherEncodingWhenEncodingNotProvided() throws ZError { + publisher.put(ZBytes.from("Test")); + assertEquals(1, receivedSamples.size()); + assertEquals(Encoding.ZENOH_STRING, receivedSamples.get(0).getEncoding()); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/PutTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/PutTest.java new file mode 100644 index 00000000..71b8cb5c --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/PutTest.java @@ -0,0 +1,54 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.bytes.Encoding; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.sample.Sample; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(JUnit4.class) +public class PutTest { + + public static final String TEST_KEY_EXP = "example/testing/keyexpr"; + public static final ZBytes TEST_PAYLOAD = ZBytes.from("Hello"); + + @Test + public void putTest() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + Sample[] receivedSample = new Sample[1]; + var keyExpr = KeyExpr.tryFrom(TEST_KEY_EXP); + + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var putOptions = new PutOptions(); + putOptions.setEncoding(Encoding.TEXT_PLAIN); + session.put(keyExpr, TEST_PAYLOAD, putOptions); + subscriber.close(); + session.close(); + assertNotNull(receivedSample[0]); + assertEquals(TEST_PAYLOAD, receivedSample[0].getPayload()); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java new file mode 100644 index 00000000..b1e5918c --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/QueryableTest.java @@ -0,0 +1,267 @@ +package io.zenoh; + +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.handlers.Handler; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.query.*; +import io.zenoh.qos.CongestionControl; +import io.zenoh.qos.Priority; +import io.zenoh.qos.QoS; +import io.zenoh.sample.Sample; +import io.zenoh.sample.SampleKind; +import org.apache.commons.net.ntp.TimeStamp; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class QueryableTest { + + private static final ZBytes testPayload = ZBytes.from("Hello queryable"); + private Session session; + private KeyExpr testKeyExpr; + + @Before + public void setUp() throws ZError { + session = Zenoh.open(Config.loadDefault()); + testKeyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + } + + @After + public void tearDown() throws ZError { + session.close(); + testKeyExpr.close(); + } + + @Test + public void queryableRunsWithCallback() throws ZError { + var timestamp = new TimeStamp(Date.from(Instant.now())); + + var sample = new Sample( + testKeyExpr, + testPayload, + Encoding.defaultEncoding(), + SampleKind.PUT, + timestamp, + new QoS(), + null + ); + + var queryable = session.declareQueryable(testKeyExpr, query -> + { + try { + var options = new ReplyOptions(); + options.setTimeStamp(timestamp); + query.reply(testKeyExpr, testPayload, options); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + Reply[] reply = new Reply[1]; + session.get(testKeyExpr.into(), reply1 -> reply[0] = reply1); + + assertNotNull(reply[0]); + Sample receivedSample = ((Reply.Success) reply[0]).getSample(); + assertEquals(sample, receivedSample); + queryable.close(); + } + + @Test + public void queryableRunsWithHandler() throws ZError, InterruptedException { + var queryable = session.declareQueryable(testKeyExpr, new QueryHandler()); + + Thread.sleep(500); + + Reply[] reply = new Reply[1]; + session.get(testKeyExpr.into(), reply1 -> reply[0] = reply1, new GetOptions()); + + Thread.sleep(500); + + queryable.close(); + assertTrue(reply[0] instanceof Reply.Success); + } + + @Test + public void queryTest() throws ZError, InterruptedException { + Query[] receivedQuery = new Query[1]; + var queryable = session.declareQueryable(testKeyExpr, query -> receivedQuery[0] = query); + + session.get(testKeyExpr); + + Thread.sleep(100); + + Query query = receivedQuery[0]; + assertNotNull(query); + assertNull(query.getPayload()); + assertNull(query.getEncoding()); + assertNull(query.getAttachment()); + + receivedQuery[0] = null; + var payload = ZBytes.from("Test value"); + var attachment = ZBytes.from("Attachment"); + + var getOptions = new GetOptions(); + getOptions.setAttachment(attachment); + getOptions.setPayload(payload); + getOptions.setEncoding(Encoding.ZENOH_STRING); + getOptions.setAttachment(attachment); + session.get(testKeyExpr, getOptions); + + Thread.sleep(100); + + query = receivedQuery[0]; + assertNotNull(query); + assertEquals(payload, query.getPayload()); + assertEquals(Encoding.ZENOH_STRING, query.getEncoding()); + assertEquals(attachment, query.getAttachment()); + + queryable.close(); + } + + @Test + public void queryReplySuccessTest() throws ZError { + var message = ZBytes.from("Test message"); + var timestamp = TimeStamp.getCurrentTime(); + + Queryable queryable = session.declareQueryable(testKeyExpr, query -> { + var options = new ReplyOptions(); + options.setTimeStamp(timestamp); + options.setPriority(Priority.DATA_HIGH); + options.setCongestionControl(CongestionControl.DROP); + options.setExpress(true); + try { + query.reply(testKeyExpr, message, options); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + Reply[] receivedReply = new Reply[1]; + session.get(testKeyExpr, reply -> receivedReply[0] = reply); + + queryable.close(); + + assertNotNull(receivedReply[0]); + assertTrue(receivedReply[0] instanceof Reply.Success); + + var sample = ((Reply.Success) receivedReply[0]).getSample(); + assertEquals(message, sample.getPayload()); + assertEquals(timestamp, sample.getTimestamp()); + assertEquals(Priority.DATA_HIGH, sample.getPriority()); + assertTrue(sample.getQos().getExpress()); + assertEquals(CongestionControl.DROP, sample.getCongestionControl()); + } + + @Test + public void queryReplyErrorTest() throws ZError, InterruptedException { + var errorMessage = ZBytes.from("Error message"); + + var queryable = session.declareQueryable(testKeyExpr, query -> + { + try { + query.replyErr(errorMessage); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + ); + + Reply[] receivedReply = new Reply[1]; + session.get(testKeyExpr, reply -> receivedReply[0] = reply); + + Thread.sleep(1000); + queryable.close(); + + assertNotNull(receivedReply[0]); + assertTrue(receivedReply[0] instanceof Reply.Error); + + var errorReply = (Reply.Error) receivedReply[0]; + assertEquals(errorMessage, errorReply.getError()); + } + + @Test + public void queryReplyDeleteTest() throws ZError, InterruptedException { + var timestamp = TimeStamp.getCurrentTime(); + + var queryable = session.declareQueryable(testKeyExpr, query -> { + try { + var config = new ReplyDelOptions(); + config.setTimeStamp(timestamp); + query.replyDel(testKeyExpr, config); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + Reply[] receivedReply = new Reply[1]; + session.get(testKeyExpr, reply -> receivedReply[0] = reply); + + Thread.sleep(1000); + queryable.close(); + + assertNotNull(receivedReply[0]); + assertTrue(receivedReply[0] instanceof Reply.Success); + + var sample = ((Reply.Success) receivedReply[0]).getSample(); + assertEquals(SampleKind.DELETE, sample.getKind()); + assertEquals(timestamp, sample.getTimestamp()); + } +} + +class QueryHandler implements Handler { + + private int counter = 0; + private final ArrayList performedReplies = new ArrayList<>(); + + public ArrayList getPerformedReplies() { + return performedReplies; + } + + @Override + public void handle(Query query) { + try { + reply(query); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + @Override + public QueryHandler receiver() { + return this; + } + + @Override + public void onClose() { + // No action needed on close + } + + public void reply(Query query) throws ZError { + ZBytes payload = ZBytes.from("Hello queryable " + counter + "!"); + counter++; + Sample sample = new Sample( + query.getKeyExpr(), + payload, + Encoding.defaultEncoding(), + SampleKind.PUT, + new TimeStamp(Date.from(Instant.now())), + new QoS(), + null + ); + performedReplies.add(sample); + var config = new ReplyOptions(); + config.setTimeStamp(sample.getTimestamp()); + query.reply(query.getKeyExpr(), payload, config); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/ScoutTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/ScoutTest.java new file mode 100644 index 00000000..80530b1f --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/ScoutTest.java @@ -0,0 +1,90 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.config.WhatAmI; +import io.zenoh.exceptions.ZError; +import io.zenoh.scouting.Hello; +import io.zenoh.scouting.Scout; +import io.zenoh.scouting.ScoutOptions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.BlockingQueue; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@RunWith(JUnit4.class) +public class ScoutTest { + + @Test + public void scouting_queueTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + + Thread.sleep(1000); + + var scout = Zenoh.scout(); + + Thread.sleep(1000); + scout.close(); + + ArrayList> helloList = new ArrayList<>(); + scout.getReceiver().drainTo(helloList); + + assertTrue(helloList.size() > 1); + for (int i = 0; i < helloList.size() - 1; i++) { + assertTrue(helloList.get(i).isPresent()); + } + assertTrue(helloList.get(helloList.size() - 1).isEmpty()); + session.close(); + } + + @Test + public void scouting_callbackTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + + Hello[] hello = new Hello[1]; + Zenoh.scout(hello1 -> hello[0] = hello1); + + Thread.sleep(1000); + + assertNotNull(hello[0]); + session.close(); + } + + @Test + public void scouting_whatAmITest() throws ZError { + var scoutOptions = new ScoutOptions(); + scoutOptions.setWhatAmI(Set.of(WhatAmI.Client, WhatAmI.Peer)); + var scout = Zenoh.scout(scoutOptions); + scout.close(); + } + + @Test + public void scouting_onCloseTest() throws ZError { + var scout = Zenoh.scout(); + var receiver = scout.getReceiver(); + + scout.close(); + var element = receiver.poll(); + assertNotNull(element); + assertTrue(element.isEmpty()); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SelectorTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SelectorTest.java new file mode 100644 index 00000000..9252dd3f --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SelectorTest.java @@ -0,0 +1,50 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.query.Parameters; +import io.zenoh.query.Selector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class SelectorTest { + + @Test + public void selector_fromStringTest() throws ZError { + var selector = Selector.tryFrom("a/b/c?arg1=val1"); + assertEquals("a/b/c", selector.getKeyExpr().toString()); + assertNotNull(selector.getParameters()); + assertEquals("arg1=val1", selector.getParameters().toString()); + + var selector2 = Selector.tryFrom("a/b/c"); + assertEquals("a/b/c", selector2.getKeyExpr().toString()); + assertNull(selector2.getParameters()); + + assertThrows(ZError.class, () -> Selector.tryFrom("")); + } + + @Test + public void parametersTest() { + var parameters = Parameters.from("a=1;b=2;c=1|2|3"); + assertEquals(List.of("1", "2", "3"), parameters.values("c")); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SessionInfoTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SessionInfoTest.java new file mode 100644 index 00000000..7a3c9a89 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SessionInfoTest.java @@ -0,0 +1,111 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.config.ZenohId; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(JUnit4.class) +public class SessionInfoTest { + + @Test + public void peersZidTest() throws Exception { + String jsonConfig = "{\n" + + " mode: \"peer\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " }\n" + + "}"; + + Config listenConfig = Config.fromJson("{\n" + + " mode: \"peer\",\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " }\n" + + "}"); + + Session sessionC = Zenoh.open(listenConfig); + Session sessionA = Zenoh.open(Config.fromJson(jsonConfig)); + Session sessionB = Zenoh.open(Config.fromJson(jsonConfig)); + + ZenohId idA = sessionA.info().zid(); + ZenohId idB = sessionB.info().zid(); + var peers = sessionC.info().peersZid(); + assertTrue(peers.contains(idA)); + assertTrue(peers.contains(idB)); + + sessionA.close(); + sessionB.close(); + sessionC.close(); + } + + @Test + public void routersZidTest() throws Exception { + Session session = Zenoh.open(Config.fromJson("{\n" + + " mode: \"router\",\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " }\n" + + "}")); + + Session connectedRouterA = Zenoh.open(Config.fromJson("{\n" + + " mode: \"router\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " },\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7451\"]\n" + + " }\n" + + "}")); + + Session connectedRouterB = Zenoh.open(Config.fromJson("{\n" + + " mode: \"router\",\n" + + " connect: {\n" + + " endpoints: [\"tcp/localhost:7450\"]\n" + + " },\n" + + " listen: {\n" + + " endpoints: [\"tcp/localhost:7452\"]\n" + + " }\n" + + "}")); + + ZenohId idA = connectedRouterA.info().zid(); + ZenohId idB = connectedRouterB.info().zid(); + + var routers = session.info().routersZid(); + + assertTrue(routers.contains(idA)); + assertTrue(routers.contains(idB)); + + connectedRouterA.close(); + connectedRouterB.close(); + session.close(); + } + + @Test + public void zidTest() throws Exception { + String jsonConfig = "{\n" + + " id: \"123456\"\n" + + "}"; + + Session session = Zenoh.open(Config.fromJson(jsonConfig)); + assertEquals("123456", session.info().zid().toString()); + session.close(); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java new file mode 100644 index 00000000..18e3b855 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SessionTest.java @@ -0,0 +1,72 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.Publisher; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class SessionTest { + + private static final KeyExpr testKeyExpr; + + static { + try { + testKeyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + @Test + public void sessionStartCloseTest() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + assertFalse(session.isClosed()); + session.close(); + assertTrue(session.isClosed()); + } + + @Test + public void sessionClose_declarationsAreUndeclaredAfterClosingSessionTest() throws ZError, InterruptedException { + Session session = Zenoh.open(Config.loadDefault()); + + Publisher publisher = session.declarePublisher(testKeyExpr); + var subscriber = session.declareSubscriber(testKeyExpr); + session.close(); + + Thread.sleep(1000); + + assertFalse(subscriber.isValid()); + assertFalse(publisher.isValid()); + + assertThrows(ZError.class, () -> publisher.put(ZBytes.from("Test"))); + } + + @Test + public void sessionClose_newDeclarationsReturnNullAfterClosingSession() throws ZError { + Session session = Zenoh.open(Config.loadDefault()); + session.close(); + assertThrows(ZError.class, () -> session.declarePublisher(testKeyExpr)); + assertThrows(ZError.class, () -> session.declareQueryable(testKeyExpr)); + assertThrows(ZError.class, () -> session.declareSubscriber(testKeyExpr)); + } +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java new file mode 100644 index 00000000..809c4bc0 --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/SubscriberTest.java @@ -0,0 +1,155 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.bytes.Encoding; +import io.zenoh.bytes.ZBytes; +import io.zenoh.exceptions.ZError; +import io.zenoh.handlers.Handler; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.qos.CongestionControl; +import io.zenoh.qos.Priority; +import io.zenoh.sample.Sample; +import kotlin.Pair; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayDeque; +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; + +@RunWith(JUnit4.class) +public class SubscriberTest { + + private static final Priority TEST_PRIORITY = Priority.DATA_HIGH; + private static final CongestionControl TEST_CONGESTION_CONTROL = CongestionControl.BLOCK; + private static final ArrayList> TEST_VALUES = new ArrayList<>(); + private static final KeyExpr testKeyExpr; + + static { + TEST_VALUES.add(new Pair<>(ZBytes.from("Test 1"), Encoding.TEXT_PLAIN)); + TEST_VALUES.add(new Pair<>(ZBytes.from("Test 2"), Encoding.TEXT_JSON)); + TEST_VALUES.add(new Pair<>(ZBytes.from("Test 3"), Encoding.TEXT_CSV)); + try { + testKeyExpr = KeyExpr.tryFrom("example/testing/keyexpr"); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + private Session session = null; + + @Before + public void setUp() throws ZError { + session = Zenoh.open(Config.loadDefault()); + } + + @After + public void tearDown() { + session.close(); + } + + @Test + public void subscriber_runsWithCallback() throws ZError { + var receivedSamples = new ArrayList(); + + var subscriber = + session.declareSubscriber(testKeyExpr, receivedSamples::add); + + TEST_VALUES.forEach(value -> { + try { + var putOptions = new PutOptions(); + putOptions.setEncoding(value.getSecond()); + putOptions.setCongestionControl(TEST_CONGESTION_CONTROL); + putOptions.setPriority(TEST_PRIORITY); + session.put(testKeyExpr, value.getFirst(), putOptions); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + ); + assertEquals(receivedSamples.size(), TEST_VALUES.size()); + + for (int i = 0; i < TEST_VALUES.size(); i++) { + var valueSent = TEST_VALUES.get(i); + var valueRecv = receivedSamples.get(i); + + assertEquals(valueRecv.getPayload(), valueSent.getFirst()); + assertEquals(valueRecv.getEncoding(), valueSent.getSecond()); + assertEquals(valueRecv.getPriority(), TEST_PRIORITY); + assertEquals(valueRecv.getCongestionControl(), TEST_CONGESTION_CONTROL); + } + + subscriber.close(); + } + + @Test + public void subscriber_runsWithHandler() throws ZError { + var handler = new QueueHandler(); + var subscriber = + session.declareSubscriber(testKeyExpr, handler); + + TEST_VALUES.forEach(value -> { + try { + var putOptions = new PutOptions(); + putOptions.setEncoding(value.getSecond()); + putOptions.setCongestionControl(TEST_CONGESTION_CONTROL); + putOptions.setPriority(TEST_PRIORITY); + + session.put(testKeyExpr, value.getFirst(), putOptions); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + ); + assertEquals(handler.queue.size(), TEST_VALUES.size()); + + for (int i = 0; i < TEST_VALUES.size(); i++) { + var valueSent = TEST_VALUES.get(i); + var valueRecv = handler.queue.poll(); + + assert valueRecv != null; + assertEquals(valueRecv.getPayload(), valueSent.getFirst()); + assertEquals(valueRecv.getEncoding(), valueSent.getSecond()); + assertEquals(valueRecv.getPriority(), TEST_PRIORITY); + assertEquals(valueRecv.getCongestionControl(), TEST_CONGESTION_CONTROL); + } + + subscriber.close(); + } +} + +class QueueHandler implements Handler> { + + final ArrayDeque queue = new ArrayDeque<>(); + + @Override + public void handle(T t) { + queue.add(t); + } + + @Override + public ArrayDeque receiver() { + return queue; + } + + @Override + public void onClose() {} +} diff --git a/zenoh-java/src/jvmTest/java/io/zenoh/UserAttachmentTest.java b/zenoh-java/src/jvmTest/java/io/zenoh/UserAttachmentTest.java new file mode 100644 index 00000000..1f0a4d2f --- /dev/null +++ b/zenoh-java/src/jvmTest/java/io/zenoh/UserAttachmentTest.java @@ -0,0 +1,219 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +package io.zenoh; + +import io.zenoh.exceptions.ZError; +import io.zenoh.keyexpr.KeyExpr; +import io.zenoh.bytes.ZBytes; +import io.zenoh.pubsub.DeleteOptions; +import io.zenoh.pubsub.PutOptions; +import io.zenoh.pubsub.Subscriber; +import io.zenoh.query.GetOptions; +import io.zenoh.query.Reply; +import io.zenoh.query.ReplyOptions; +import io.zenoh.sample.Sample; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Duration; + +import static org.junit.Assert.*; + +@RunWith(JUnit4.class) +public class UserAttachmentTest { + + static final KeyExpr keyExpr; + static final ZBytes payload = ZBytes.from("test payload"); + static final ZBytes attachment = ZBytes.from("mock_attachment"); + static { + try { + keyExpr = KeyExpr.tryFrom("example/testing/attachment"); + } catch (ZError e) { + throw new RuntimeException(e); + } + } + + Session session; + + @Before + public void setup() throws ZError { + session = Zenoh.open(Config.loadDefault()); + } + + @After + public void tearDown() { + session.close(); + } + + @Test + public void putWithAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var putOptions = new PutOptions(); + putOptions.setAttachment(attachment); + session.put(keyExpr, payload, putOptions); + + subscriber.close(); + + assertNotNull(receivedSample[0]); + ZBytes receivedAttachment = receivedSample[0].getAttachment(); + assertEquals(attachment, receivedAttachment); + } + + @Test + public void publisherPutWithAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + var publisher = session.declarePublisher(keyExpr); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var putOptions = new PutOptions(); + putOptions.setAttachment(attachment); + publisher.put(payload, putOptions); + + publisher.close(); + subscriber.close(); + + assertNotNull(receivedSample[0]); + ZBytes receivedAttachment = receivedSample[0].getAttachment(); + assertEquals(attachment, receivedAttachment); + } + + @Test + public void publisherPutWithoutAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + var publisher = session.declarePublisher(keyExpr); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + publisher.put(payload); + + publisher.close(); + subscriber.close(); + + assertNotNull(receivedSample[0]); + assertNull(receivedSample[0].getAttachment()); + } + + @Test + public void publisherDeleteWithAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + var publisher = session.declarePublisher(keyExpr); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + var deleteOptions = new DeleteOptions(); + deleteOptions.setAttachment(attachment); + publisher.delete(deleteOptions); + + publisher.close(); + subscriber.close(); + + assertNotNull(receivedSample[0]); + ZBytes receivedAttachment = receivedSample[0].getAttachment(); + assertEquals(attachment, receivedAttachment); + } + + @Test + public void publisherDeleteWithoutAttachmentTest() throws ZError { + Sample[] receivedSample = new Sample[1]; + var publisher = session.declarePublisher(keyExpr); + Subscriber subscriber = + session.declareSubscriber(keyExpr, sample -> receivedSample[0] = sample); + + publisher.delete(); + + publisher.close(); + subscriber.close(); + + assertNotNull(receivedSample[0]); + assertNull(receivedSample[0].getAttachment()); + } + + @Test + public void queryWithAttachmentTest() throws ZError { + ZBytes[] receivedAttachment = new ZBytes[1]; + var queryable = session.declareQueryable(keyExpr, query -> { + receivedAttachment[0] = query.getAttachment(); + try { + query.reply(keyExpr, payload); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + getOptions.setAttachment(attachment); + session.get(keyExpr, getOptions); + + queryable.close(); + + assertNotNull(receivedAttachment[0]); + assertEquals(attachment, receivedAttachment[0]); + } + + @Test + public void queryReplyWithAttachmentTest() throws ZError { + Reply[] reply = new Reply[1]; + var queryable = session.declareQueryable(keyExpr, query -> { + try { + var options = new ReplyOptions(); + options.setAttachment(attachment); + query.reply(keyExpr, payload, options); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + + + var getOptions = new GetOptions(); + getOptions.setTimeout(Duration.ofMillis(1000)); + getOptions.setAttachment(attachment); + session.get(keyExpr, reply1 -> reply[0] = reply1, getOptions); + + queryable.close(); + + Reply receivedReply = reply[0]; + assertNotNull(receivedReply); + ZBytes receivedAttachment = ((Reply.Success) receivedReply).getSample().getAttachment(); + assertEquals(attachment, receivedAttachment); + } + + @Test + public void queryReplyWithoutAttachmentTest() throws ZError { + Reply[] reply = new Reply[1]; + var queryable = session.declareQueryable(keyExpr, query -> { + try { + query.reply(keyExpr, payload); + } catch (ZError e) { + throw new RuntimeException(e); + } + }); + session.get(keyExpr, reply1 -> reply[0] = reply1); + + queryable.close(); + + Reply receivedReply = reply[0]; + assertNotNull(receivedReply); + ZBytes receivedAttachment = ((Reply.Success) receivedReply).getSample().getAttachment(); + assertNull(receivedAttachment); + } +} diff --git a/zenoh-jni/Cargo.lock b/zenoh-jni/Cargo.lock index 068de54a..96736838 100644 --- a/zenoh-jni/Cargo.lock +++ b/zenoh-jni/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aes" @@ -43,24 +43,24 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-logd-logger" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fe8042a3174caeafdad8ee1337788db51833e7f8649c07c6d6de70048adef4" +checksum = "0483169d5fac0887f85c2fa8fecfe08669791712d8260de1a6ec30630a62932f" dependencies = [ "bytes", "env_logger", @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "array-init" @@ -88,9 +88,9 @@ checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" [[package]] name = "asn1-rs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -104,13 +104,13 @@ dependencies = [ [[package]] name = "asn1-rs-derive" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", "synstructure", ] @@ -122,7 +122,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -133,13 +133,13 @@ checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -155,30 +155,30 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] name = "base64" -version = "0.21.4" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" @@ -209,9 +209,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -227,15 +227,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -251,11 +251,11 @@ checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" [[package]] name = "cc" -version = "1.0.83" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "45bcde016d64c21da4be18b655631e5ab6d3107607e71a73a9f53eb48aae23fb" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -331,24 +331,24 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" dependencies = [ "proc-macro2", "quote", @@ -367,15 +367,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -398,15 +398,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -471,20 +471,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] name = "dyn-clone" -version = "1.0.13" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" @@ -621,7 +621,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "git-version" @@ -700,15 +700,9 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - [[package]] name = "hashbrown" version = "0.12.3" @@ -760,9 +754,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -771,9 +765,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "humantime" @@ -803,9 +797,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -829,12 +823,6 @@ dependencies = [ "serde", ] -[[package]] -name = "iter-read" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c397ca3ea05ad509c4ec451fea28b4771236a376ca1c69fd5143aae0cf8f93c4" - [[package]] name = "itertools" version = "0.13.0" @@ -846,9 +834,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -888,9 +876,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -908,9 +896,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -933,6 +921,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.158" @@ -941,25 +935,35 @@ checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libloading" -version = "0.8.0" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.6", ] [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -967,15 +971,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lz4_flex" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" dependencies = [ "twox-hash", ] @@ -1003,11 +1007,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -1037,7 +1041,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", @@ -1071,11 +1075,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1145,18 +1148,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] [[package]] name = "oid-registry" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ "asn1-rs", ] @@ -1193,15 +1196,15 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1209,15 +1212,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.5.4", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1243,9 +1246,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.3" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" dependencies = [ "memchr", "thiserror", @@ -1254,9 +1257,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.3" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" dependencies = [ "pest", "pest_generator", @@ -1264,22 +1267,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.3" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] name = "pest_meta" -version = "2.7.3" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" dependencies = [ "once_cell", "pest", @@ -1293,7 +1296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.4.0", + "indexmap 2.5.0", ] [[package]] @@ -1326,7 +1329,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -1340,29 +1343,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1431,9 +1434,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" @@ -1446,9 +1452,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", @@ -1482,15 +1488,15 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1534,39 +1540,30 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] @@ -1616,16 +1613,17 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "ring" -version = "0.17.6" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1644,24 +1642,22 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.21.4", - "bitflags 2.5.0", + "base64 0.21.7", + "bitflags 2.6.0", "serde", "serde_derive", ] [[package]] name = "rsa" -version = "0.9.2" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ - "byteorder", "const-oid", "digest", "num-bigint-dig", "num-integer", - "num-iter", "num-traits", "pkcs1", "pkcs8", @@ -1674,9 +1670,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -1704,9 +1700,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "log", "once_cell", @@ -1719,9 +1715,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -1748,9 +1744,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-platform-verifier" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" dependencies = [ "core-foundation", "core-foundation-sys", @@ -1769,15 +1765,15 @@ dependencies = [ [[package]] name = "rustls-platform-verifier-android" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1786,9 +1782,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1801,11 +1797,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1830,7 +1826,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -1851,11 +1847,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -1865,9 +1861,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -1875,51 +1871,28 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-pickle" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762ad136a26407c6a80825813600ceeab5e613660d93d79a41f0ec877171e71" -dependencies = [ - "byteorder", - "iter-read", - "num-bigint", - "num-traits", - "serde", -] - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -1930,14 +1903,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1951,7 +1924,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.5.0", "itoa", "ryu", "serde", @@ -1960,9 +1933,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1971,9 +1944,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2008,11 +1981,17 @@ dependencies = [ "dirs", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", @@ -2060,9 +2039,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -2082,9 +2061,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2099,9 +2078,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -2116,7 +2095,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -2136,22 +2115,22 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -2197,9 +2176,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2221,9 +2200,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -2243,7 +2222,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] @@ -2259,9 +2238,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", @@ -2271,9 +2250,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2286,11 +2265,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2299,20 +2277,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -2362,9 +2340,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", "bytes", @@ -2390,9 +2368,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" @@ -2422,9 +2400,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -2437,9 +2415,9 @@ dependencies = [ [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "unsafe-libyaml" @@ -2453,12 +2431,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - [[package]] name = "unzip-n" version = "0.1.2" @@ -2534,15 +2506,15 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -2556,34 +2528,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2591,28 +2564,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" dependencies = [ "rustls-pki-types", ] @@ -2635,11 +2608,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2672,7 +2645,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2707,17 +2689,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2734,9 +2717,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2752,9 +2735,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2770,9 +2753,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2788,9 +2777,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2806,9 +2795,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2824,9 +2813,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2842,9 +2831,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "x509-parser" @@ -2866,7 +2855,7 @@ dependencies = [ [[package]] name = "zenoh" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "ahash", "async-trait", @@ -2875,6 +2864,7 @@ dependencies = [ "futures", "git-version", "itertools", + "json5", "lazy_static", "once_cell", "paste", @@ -2883,16 +2873,12 @@ dependencies = [ "rand", "rustc_version", "serde", - "serde-pickle", - "serde_cbor", "serde_json", - "serde_yaml", "socket2", "tokio", "tokio-util", "tracing", "uhlc", - "unwrap-infallible", "vec_map", "zenoh-buffers", "zenoh-codec", @@ -2915,7 +2901,7 @@ dependencies = [ [[package]] name = "zenoh-buffers" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "zenoh-collections", ] @@ -2923,7 +2909,7 @@ dependencies = [ [[package]] name = "zenoh-codec" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "tracing", "uhlc", @@ -2934,14 +2920,13 @@ dependencies = [ [[package]] name = "zenoh-collections" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" [[package]] name = "zenoh-config" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ - "flume 0.11.0", "json5", "num_cpus", "secrecy", @@ -2961,7 +2946,7 @@ dependencies = [ [[package]] name = "zenoh-core" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "lazy_static", "tokio", @@ -2972,7 +2957,7 @@ dependencies = [ [[package]] name = "zenoh-crypto" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "aes", "hmac", @@ -2985,11 +2970,12 @@ dependencies = [ [[package]] name = "zenoh-ext" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "bincode", "flume 0.11.0", "futures", + "leb128", "serde", "tokio", "tracing", @@ -3001,7 +2987,7 @@ dependencies = [ [[package]] name = "zenoh-keyexpr" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "hashbrown 0.14.5", "keyed-set", @@ -3015,7 +3001,7 @@ dependencies = [ [[package]] name = "zenoh-link" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "zenoh-config", "zenoh-link-commons", @@ -3032,7 +3018,7 @@ dependencies = [ [[package]] name = "zenoh-link-commons" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "flume 0.11.0", @@ -3055,7 +3041,7 @@ dependencies = [ [[package]] name = "zenoh-link-quic" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "base64 0.22.1", @@ -3080,7 +3066,7 @@ dependencies = [ [[package]] name = "zenoh-link-tcp" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "socket2", @@ -3097,7 +3083,7 @@ dependencies = [ [[package]] name = "zenoh-link-tls" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "base64 0.22.1", @@ -3124,7 +3110,7 @@ dependencies = [ [[package]] name = "zenoh-link-udp" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "socket2", @@ -3143,7 +3129,7 @@ dependencies = [ [[package]] name = "zenoh-link-unixsock_stream" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "nix", @@ -3161,7 +3147,7 @@ dependencies = [ [[package]] name = "zenoh-link-ws" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "futures-util", @@ -3181,23 +3167,24 @@ dependencies = [ [[package]] name = "zenoh-macros" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", "zenoh-keyexpr", ] [[package]] name = "zenoh-plugin-trait" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "git-version", "libloading", "serde", "tracing", + "zenoh-config", "zenoh-keyexpr", "zenoh-macros", "zenoh-result", @@ -3207,7 +3194,7 @@ dependencies = [ [[package]] name = "zenoh-protocol" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "const_format", "rand", @@ -3221,7 +3208,7 @@ dependencies = [ [[package]] name = "zenoh-result" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "anyhow", ] @@ -3229,7 +3216,7 @@ dependencies = [ [[package]] name = "zenoh-runtime" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "lazy_static", "ron", @@ -3242,7 +3229,7 @@ dependencies = [ [[package]] name = "zenoh-sync" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "event-listener", "futures", @@ -3255,7 +3242,7 @@ dependencies = [ [[package]] name = "zenoh-task" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "futures", "tokio", @@ -3268,7 +3255,7 @@ dependencies = [ [[package]] name = "zenoh-transport" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "crossbeam-utils", @@ -3301,7 +3288,7 @@ dependencies = [ [[package]] name = "zenoh-util" version = "1.0.0-dev" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#e72e4d46cc21f3eea7d9f25591407d66c96265b7" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#1a4a295098cf4c8d65b252883e9692b8c4dd7d1b" dependencies = [ "async-trait", "const_format", @@ -3334,35 +3321,36 @@ dependencies = [ "jni 0.21.1", "json5", "rustc_version", + "serde_yaml", "tracing", "uhlc", "zenoh", "zenoh-ext", - "zenoh-protocol", ] [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.77", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/zenoh-jni/Cargo.toml b/zenoh-jni/Cargo.toml index c5f62f83..08246e28 100644 --- a/zenoh-jni/Cargo.toml +++ b/zenoh-jni/Cargo.toml @@ -24,7 +24,7 @@ description = "Zenoh: Zero Overhead Pub/sub, Store/Query and Compute." name = "zenoh_jni" [features] -default = ["zenoh/default", "zenoh-ext/default"] +default = ["zenoh/default", "zenoh-ext"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -35,10 +35,10 @@ jni = "0.21.1" flume = "0.10.14" uhlc = "0.8.0" json5 = "0.4.1" -zenoh = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", default-features = false } -zenoh-ext = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", default-features = false } -zenoh-protocol = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", default-features = false } -tracing = "0.1" +serde_yaml = "0.9.19" +zenoh = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false } +zenoh-ext = { version = "1.0.0-dev", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["internal"], default-features = false, optional = true } +tracing = { version = "0.1" , features = ["log"] } [lib] name = "zenoh_jni" crate_type = ["staticlib", "dylib"] diff --git a/zenoh-jni/src/config.rs b/zenoh-jni/src/config.rs new file mode 100644 index 00000000..0ada1340 --- /dev/null +++ b/zenoh-jni/src/config.rs @@ -0,0 +1,185 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::{ptr::null, sync::Arc}; + +use jni::{ + objects::{JClass, JString}, + sys::jstring, + JNIEnv, +}; +use zenoh::Config; + +use crate::{errors::ZResult, zerror}; +use crate::{throw_exception, utils::decode_string}; + +/// Loads the default configuration, returning a raw pointer to it. +/// +/// The pointer to the config is expected to be freed later on upon the destruction of the +/// Kotlin Config instance. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadDefaultConfigViaJNI( + _env: JNIEnv, + _class: JClass, +) -> *const Config { + let config = Config::default(); + Arc::into_raw(Arc::new(config)) +} + +/// Loads the config from a file, returning a pointer to the loaded config in case of success. +/// In case of failure, an exception is thrown via JNI. +/// +/// The pointer to the config is expected to be freed later on upon the destruction of the +/// Kotlin Config instance. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadConfigFileViaJNI( + mut env: JNIEnv, + _class: JClass, + config_path: JString, +) -> *const Config { + || -> ZResult<*const Config> { + let config_file_path = decode_string(&mut env, &config_path)?; + let config = Config::from_file(config_file_path).map_err(|err| zerror!(err))?; + Ok(Arc::into_raw(Arc::new(config))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Loads the config from a json/json5 formatted string, returning a pointer to the loaded config +/// in case of success. In case of failure, an exception is thrown via JNI. +/// +/// The pointer to the config is expected to be freed later on upon the destruction of the +/// Kotlin Config instance. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadJsonConfigViaJNI( + mut env: JNIEnv, + _class: JClass, + json_config: JString, +) -> *const Config { + || -> ZResult<*const Config> { + let json_config = decode_string(&mut env, &json_config)?; + let mut deserializer = + json5::Deserializer::from_str(&json_config).map_err(|err| zerror!(err))?; + let config = Config::from_deserializer(&mut deserializer).map_err(|err| match err { + Ok(c) => zerror!("Invalid configuration: {}", c), + Err(e) => zerror!("JSON error: {}", e), + })?; + Ok(Arc::into_raw(Arc::new(config))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Loads the config from a yaml-formatted string, returning a pointer to the loaded config +/// in case of success. In case of failure, an exception is thrown via JNI. +/// +/// The pointer to the config is expected to be freed later on upon the destruction of the +/// Kotlin Config instance. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadYamlConfigViaJNI( + mut env: JNIEnv, + _class: JClass, + yaml_config: JString, +) -> *const Config { + || -> ZResult<*const Config> { + let yaml_config = decode_string(&mut env, &yaml_config)?; + let deserializer = serde_yaml::Deserializer::from_str(&yaml_config); + let config = Config::from_deserializer(deserializer).map_err(|err| match err { + Ok(c) => zerror!("Invalid configuration: {}", c), + Err(e) => zerror!("YAML error: {}", e), + })?; + Ok(Arc::into_raw(Arc::new(config))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Returns the json value associated to the provided [key]. May throw an exception in case of failure, which must be handled +/// on the kotlin layer. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_getJsonViaJNI( + mut env: JNIEnv, + _class: JClass, + cfg_ptr: *const Config, + key: JString, +) -> jstring { + let arc_cfg: Arc = Arc::from_raw(cfg_ptr); + let result = || -> ZResult { + let key = decode_string(&mut env, &key)?; + let json = arc_cfg.get_json(&key).map_err(|err| zerror!(err))?; + let java_json = env.new_string(json).map_err(|err| zerror!(err))?; + Ok(java_json.as_raw()) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default().as_raw() + }); + std::mem::forget(arc_cfg); + result +} + +/// Inserts a json5 value associated to the provided [key]. May throw an exception in case of failure, which must be handled +/// on the kotlin layer. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_insertJson5ViaJNI( + mut env: JNIEnv, + _class: JClass, + cfg_ptr: *const Config, + key: JString, + value: JString, +) { + || -> ZResult<()> { + let key = decode_string(&mut env, &key)?; + let value = decode_string(&mut env, &value)?; + let mut config = core::ptr::read(cfg_ptr); + let insert_result = config + .insert_json5(&key, &value) + .map_err(|err| zerror!(err)); + core::ptr::write(cfg_ptr as *mut _, config); + insert_result + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + }) +} + +/// Frees the pointer to the config. The pointer should be valid and should have been obtained through +/// one of the preceding `load` functions. This function should be called upon destruction of the kotlin +/// Config instance. +#[no_mangle] +#[allow(non_snake_case)] +pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_freePtrViaJNI( + _env: JNIEnv, + _: JClass, + config_ptr: *const Config, +) { + Arc::from_raw(config_ptr); +} diff --git a/zenoh-jni/src/errors.rs b/zenoh-jni/src/errors.rs index edca4ab8..23687d4c 100644 --- a/zenoh-jni/src/errors.rs +++ b/zenoh-jni/src/errors.rs @@ -26,71 +26,34 @@ macro_rules! throw_exception { } #[macro_export] -macro_rules! jni_error { +macro_rules! zerror { ($arg:expr) => { - Error::Jni($arg.to_string()) + $crate::errors::ZError($arg.to_string()) }; ($fmt:expr, $($arg:tt)*) => { - Error::Jni(format!($fmt, $($arg)*)) + $crate::errors::ZError(format!($fmt, $($arg)*)) }; } -#[macro_export] -macro_rules! session_error { - ($arg:expr) => { - $crate::errors::Error::Session($arg.to_string()) - }; - ($fmt:expr, $($arg:tt)*) => { - Error::Session(format!($fmt, $($arg)*)) - }; - -} - -#[macro_export] -macro_rules! key_expr_error { - ($arg:expr) => { - Error::KeyExpr($arg.to_string()) - }; - ($fmt:expr, $($arg:tt)*) => { - Error::KeyExpr(format!($fmt, $($arg)*)) - }; -} - -pub(crate) type Result = core::result::Result; +pub(crate) type ZResult = core::result::Result; #[derive(Debug)] -pub(crate) enum Error { - Session(String), - KeyExpr(String), - Jni(String), -} +pub(crate) struct ZError(pub String); -impl fmt::Display for Error { +impl fmt::Display for ZError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Session(msg) => write!(f, "{}", msg), - Error::KeyExpr(msg) => write!(f, "{}", msg), - Error::Jni(msg) => write!(f, "{}", msg), - } + write!(f, "{}", self.0) } } -impl Error { - fn get_associated_kotlin_exception(&self) -> String { - let class = match self { - Error::Session(_) => "io/zenoh/exceptions/SessionException", - Error::KeyExpr(_) => "io/zenoh/exceptions/KeyExprException", - Error::Jni(_) => "io/zenoh/exceptions/JNIException", - }; - class.to_string() - } +impl ZError { + const KOTLIN_EXCEPTION_NAME: &'static str = "io/zenoh/exceptions/ZError"; - pub fn throw_on_jvm(&self, env: &mut JNIEnv) -> Result<()> { - let exception_name = self.get_associated_kotlin_exception(); + pub fn throw_on_jvm(&self, env: &mut JNIEnv) -> ZResult<()> { let exception_class = env - .find_class(&exception_name) - .map_err(|err| jni_error!("Failed to retrieve exception class: {}", err))?; + .find_class(Self::KOTLIN_EXCEPTION_NAME) + .map_err(|err| zerror!("Failed to retrieve exception class: {}", err))?; env.throw_new(exception_class, self.to_string()) - .map_err(|err| jni_error!("Failed to throw exception: {}", err)) + .map_err(|err| zerror!("Failed to throw exception: {}", err)) } } diff --git a/zenoh-jni/src/key_expr.rs b/zenoh-jni/src/key_expr.rs index b4fd0fe9..06a1b6c6 100644 --- a/zenoh-jni/src/key_expr.rs +++ b/zenoh-jni/src/key_expr.rs @@ -16,14 +16,13 @@ use std::ops::Deref; use std::sync::Arc; use jni::objects::JClass; -use jni::sys::{jboolean, jstring}; +use jni::sys::{jboolean, jint, jstring}; use jni::{objects::JString, JNIEnv}; use zenoh::key_expr::KeyExpr; -use crate::errors::Error; -use crate::errors::Result; +use crate::errors::ZResult; use crate::utils::decode_string; -use crate::{jni_error, key_expr_error, throw_exception}; +use crate::{throw_exception, zerror}; /// Validates the provided `key_expr` to be a valid key expression, returning it back /// in case of success or throwing an exception in case of failure. @@ -40,7 +39,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_tryFromViaJNI( _class: JClass, key_expr: JString, ) -> jstring { - decode_key_expr(&mut env, &key_expr) + validate_key_expr(&mut env, &key_expr) .map(|_| **key_expr) .unwrap_or_else(|err| { throw_exception!(env, err); @@ -67,7 +66,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_autocanonizeViaJNI .and_then(|key_expr| { env.new_string(key_expr.to_string()) .map(|kexp| kexp.as_raw()) - .map_err(|err| jni_error!(err)) + .map_err(|err| zerror!(err)) }) .unwrap_or_else(|err| { throw_exception!(env, err); @@ -79,9 +78,9 @@ pub extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_autocanonizeViaJNI /// /// # Params: /// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_1`: String representation of the key expression 1. +/// - `key_expr_str_1`: String representation of the key expression 1. /// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_2`: String representation of the key expression 2. +/// - `key_expr_str_2`: String representation of the key expression 2. /// /// # Safety /// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing @@ -99,7 +98,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_intersectsV key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, key_expr_str_2: JString, ) -> jboolean { - || -> Result { + || -> ZResult { let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; Ok(key_expr_1.intersects(&key_expr_2) as jboolean) @@ -114,9 +113,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_intersectsV /// /// # Params: /// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_1`: String representation of the key expression 1. +/// - `key_expr_str_1`: String representation of the key expression 1. /// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. -/// - `key_expr_ptr_2`: String representation of the key expression 2. +/// - `key_expr_str_2`: String representation of the key expression 2. /// /// # Safety /// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing @@ -134,7 +133,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_includesVia key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, key_expr_str_2: JString, ) -> jboolean { - || -> Result { + || -> ZResult { let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; Ok(key_expr_1.includes(&key_expr_2) as jboolean) @@ -145,6 +144,120 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_includesVia }) } +/// Returns the integer representation of the intersection level of the key expression 1 and key expression 2, +/// from the perspective of key expression 1. +/// +/// # Params: +/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. +/// - `key_expr_str_1`: String representation of the key expression 1. +/// - `key_expr_ptr_2`: Pointer to the key expression 2, differs from null only if it's a declared key expr. +/// - `key_expr_str_2`: String representation of the key expression 2. +/// +/// # Safety +/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing +/// key expressions that were declared from a session (in that case the key expression has a pointer associated). +/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers +/// remain valid after the call to this function. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_relationToViaJNI( + mut env: JNIEnv, + _: JClass, + key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, + key_expr_str_1: JString, + key_expr_ptr_2: /*nullable*/ *const KeyExpr<'static>, + key_expr_str_2: JString, +) -> jint { + || -> ZResult { + let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; + let key_expr_2 = process_kotlin_key_expr(&mut env, &key_expr_str_2, key_expr_ptr_2)?; + Ok(key_expr_1.relation_to(&key_expr_2) as jint) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + -1 as jint + }) +} + +/// Joins key expression 1 with key expression 2, where key_expr_2 is a string. Returns the string representation +/// of the result, or throws an exception in case of failure. +/// +/// # Params: +/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. +/// - `key_expr_ptr_1`: String representation of the key expression 1. +/// - `key_expr_2`: String representation of the key expression 2. +/// +/// # Safety +/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing +/// key expressions that were declared from a session (in that case the key expression has a pointer associated). +/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers +/// remain valid after the call to this function. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_joinViaJNI( + mut env: JNIEnv, + _class: JClass, + key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, + key_expr_str_1: JString, + key_expr_2: JString, +) -> jstring { + || -> ZResult { + let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; + let key_expr_2_str = decode_string(&mut env, &key_expr_2)?; + let result = key_expr_1 + .join(key_expr_2_str.as_str()) + .map_err(|err| zerror!(err))?; + env.new_string(result.to_string()) + .map(|kexp| kexp.as_raw()) + .map_err(|err| zerror!(err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default().as_raw() + }) +} + +/// Concats key_expr_1 with key_expr_2, where key_expr_2 is a string. Returns the string representation +/// of the result, or throws an exception in case of failure. +/// +/// # Params: +/// - `key_expr_ptr_1`: Pointer to the key expression 1, differs from null only if it's a declared key expr. +/// - `key_expr_ptr_1`: String representation of the key expression 1. +/// - `key_expr_2`: String representation of the key expression 2. +/// +/// # Safety +/// - This function is marked as unsafe due to raw pointer manipulation, which happens only when providing +/// key expressions that were declared from a session (in that case the key expression has a pointer associated). +/// In that case, this function assumes the pointers are valid pointers to key expressions and those pointers +/// remain valid after the call to this function. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_00024Companion_concatViaJNI( + mut env: JNIEnv, + _class: JClass, + key_expr_ptr_1: /*nullable*/ *const KeyExpr<'static>, + key_expr_str_1: JString, + key_expr_2: JString, +) -> jstring { + || -> ZResult { + let key_expr_1 = process_kotlin_key_expr(&mut env, &key_expr_str_1, key_expr_ptr_1)?; + let key_expr_2_str = decode_string(&mut env, &key_expr_2)?; + let result = key_expr_1 + .concat(key_expr_2_str.as_str()) + .map_err(|err| zerror!(err))?; + env.new_string(result.to_string()) + .map(|kexp| kexp.as_raw()) + .map_err(|err| zerror!(err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default().as_raw() + }) +} + /// Frees a declared key expression. /// /// # Parameters @@ -167,20 +280,20 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIKeyExpr_freePtrViaJNI( Arc::from_raw(key_expr_ptr); } -fn decode_key_expr(env: &mut JNIEnv, key_expr: &JString) -> Result> { +fn validate_key_expr(env: &mut JNIEnv, key_expr: &JString) -> ZResult> { let key_expr_str = decode_string(env, key_expr) - .map_err(|err| jni_error!("Unable to get key expression string value: '{}'.", err))?; + .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err))?; KeyExpr::try_from(key_expr_str) - .map_err(|err| key_expr_error!("Unable to create key expression: '{}'.", err)) + .map_err(|err| zerror!("Unable to create key expression: '{}'.", err)) } -fn autocanonize_key_expr(env: &mut JNIEnv, key_expr: &JString) -> Result> { +fn autocanonize_key_expr(env: &mut JNIEnv, key_expr: &JString) -> ZResult> { decode_string(env, key_expr) - .map_err(|err| jni_error!("Unable to get key expression string value: '{}'.", err)) + .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err)) .and_then(|key_expr_str| { KeyExpr::autocanonize(key_expr_str) - .map_err(|err| key_expr_error!("Unable to create key expression: '{}'", err)) + .map_err(|err| zerror!("Unable to create key expression: '{}'", err)) }) } @@ -192,14 +305,20 @@ fn autocanonize_key_expr(env: &mut JNIEnv, key_expr: &JString) -> Result, -) -> Result> { +) -> ZResult> { if key_expr_ptr.is_null() { - decode_key_expr(env, key_expr_str) - .map_err(|err| jni_error!("Unable to process key expression: '{}'.", err)) + let key_expr = decode_string(env, key_expr_str) + .map_err(|err| zerror!("Unable to get key expression string value: '{}'.", err))?; + Ok(KeyExpr::from_string_unchecked(key_expr)) } else { let key_expr = Arc::from_raw(key_expr_ptr); let key_expr_clone = key_expr.deref().clone(); diff --git a/zenoh-jni/src/lib.rs b/zenoh-jni/src/lib.rs index edfba47a..bf012f13 100644 --- a/zenoh-jni/src/lib.rs +++ b/zenoh-jni/src/lib.rs @@ -12,15 +12,19 @@ // ZettaScale Zenoh Team, // +mod config; mod errors; mod key_expr; +mod liveliness; mod logger; mod publisher; mod query; mod queryable; +mod scouting; mod session; mod subscriber; mod utils; +mod zenoh_id; // Test should be runned with `cargo test --no-default-features` #[test] diff --git a/zenoh-jni/src/liveliness.rs b/zenoh-jni/src/liveliness.rs new file mode 100644 index 00000000..8b05c925 --- /dev/null +++ b/zenoh-jni/src/liveliness.rs @@ -0,0 +1,242 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::{ptr::null, sync::Arc, time::Duration}; + +use jni::{ + objects::{JByteArray, JClass, JObject, JString, JValue}, + sys::{jboolean, jint, jlong}, + JNIEnv, +}; + +use zenoh::{ + internal::runtime::ZRuntime, key_expr::KeyExpr, liveliness::LivelinessToken, + pubsub::Subscriber, sample::Sample, Session, Wait, +}; + +use crate::{ + errors::ZResult, + key_expr::process_kotlin_key_expr, + session::{on_reply_error, on_reply_success}, + throw_exception, + utils::{ + bytes_to_java_array, get_callback_global_ref, get_java_vm, load_on_close, + slice_to_java_string, + }, + zerror, +}; + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_getViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: JString, + callback: JObject, + timeout_ms: jlong, + on_close: JObject, +) { + let session = unsafe { Arc::from_raw(session_ptr) }; + let _ = || -> ZResult<()> { + let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; + let java_vm = Arc::new(get_java_vm(&mut env)?); + let callback_global_ref = get_callback_global_ref(&mut env, callback)?; + let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + let timeout = Duration::from_millis(timeout_ms as u64); + let replies = session + .liveliness() + .get(key_expr.to_owned()) + .timeout(timeout) + .wait() + .map_err(|err| zerror!(err))?; + + ZRuntime::Application.spawn(async move { + on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure + while let Ok(reply) = replies.recv_async().await { + || -> ZResult<()> { + tracing::debug!("Receiving liveliness reply through JNI: {:?}", reply); + let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { + zerror!( + "Unable to attach thread for GET liveliness query callback: {}.", + err + ) + })?; + match reply.result() { + Ok(sample) => on_reply_success( + &mut env, + reply.replier_id(), + sample, + &callback_global_ref, + ), + Err(error) => on_reply_error( + &mut env, + reply.replier_id(), + error, + &callback_global_ref, + ), + } + }() + .unwrap_or_else(|err| tracing::error!("Error on get liveliness callback: {err}.")); + } + }); + Ok(()) + }() + .map_err(|err| { + throw_exception!(env, err); + }); + std::mem::forget(session); +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareTokenViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: JString, +) -> *const LivelinessToken { + let session = unsafe { Arc::from_raw(session_ptr) }; + let ptr = || -> ZResult<*const LivelinessToken> { + let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; + tracing::trace!("Declaring liveliness token on '{key_expr}'."); + let token = session + .liveliness() + .declare_token(key_expr) + .wait() + .map_err(|err| zerror!(err))?; + Ok(Arc::into_raw(Arc::new(token))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }); + std::mem::forget(session); + ptr +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNILivelinessToken_00024Companion_undeclareViaJNI( + _env: JNIEnv, + _: JClass, + token_ptr: *const LivelinessToken, +) { + unsafe { Arc::from_raw(token_ptr) }; +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNILiveliness_declareSubscriberViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, + key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, + key_expr_str: JString, + callback: JObject, + history: jboolean, + on_close: JObject, +) -> *const Subscriber<()> { + let session = unsafe { Arc::from_raw(session_ptr) }; + || -> ZResult<*const Subscriber<()>> { + let java_vm = Arc::new(get_java_vm(&mut env)?); + let callback_global_ref = get_callback_global_ref(&mut env, callback)?; + let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + + let key_expr = unsafe { process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr) }?; + tracing::debug!("Declaring liveliness subscriber on '{}'...", key_expr); + + let result = session + .liveliness() + .declare_subscriber(key_expr.to_owned()) + .history(history != 0) + .callback(move |sample: Sample| { + let _ = || -> ZResult<()> { + on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure + let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { + zerror!("Unable to attach thread for liveliness subscriber: {}", err) + })?; + let byte_array = bytes_to_java_array(&env, sample.payload()) + .map(|array| env.auto_local(array))?; + + let encoding_id: jint = sample.encoding().id() as jint; + let encoding_schema = match sample.encoding().schema() { + Some(schema) => slice_to_java_string(&env, schema)?, + None => JString::default(), + }; + let kind = sample.kind() as jint; + let (timestamp, is_valid) = sample + .timestamp() + .map(|timestamp| (timestamp.get_time().as_u64(), true)) + .unwrap_or((0, false)); + + let attachment_bytes = sample + .attachment() + .map_or_else( + || Ok(JByteArray::default()), + |attachment| bytes_to_java_array(&env, attachment), + ) + .map(|array| env.auto_local(array)) + .map_err(|err| zerror!("Error processing attachment: {}", err))?; + + let key_expr_str = env.auto_local( + env.new_string(sample.key_expr().to_string()) + .map_err(|err| zerror!("Error processing sample key expr: {}", err))?, + ); + + let express = sample.express(); + let priority = sample.priority() as jint; + let cc = sample.congestion_control() as jint; + + env.call_method( + &callback_global_ref, + "run", + "(Ljava/lang/String;[BILjava/lang/String;IJZ[BZII)V", + &[ + JValue::from(&key_expr_str), + JValue::from(&byte_array), + JValue::from(encoding_id), + JValue::from(&encoding_schema), + JValue::from(kind), + JValue::from(timestamp as i64), + JValue::from(is_valid), + JValue::from(&attachment_bytes), + JValue::from(express), + JValue::from(priority), + JValue::from(cc), + ], + ) + .map_err(|err| zerror!(err))?; + Ok(()) + }() + .map_err(|err| tracing::error!("On liveliness subscriber callback error: {err}")); + }) + .wait(); + + let subscriber = + result.map_err(|err| zerror!("Unable to declare liveliness subscriber: {}", err))?; + + tracing::debug!("Subscriber declared on '{}'.", key_expr); + std::mem::forget(session); + Ok(Arc::into_raw(Arc::new(subscriber))) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} diff --git a/zenoh-jni/src/logger.rs b/zenoh-jni/src/logger.rs index 780e693e..73f5112c 100644 --- a/zenoh-jni/src/logger.rs +++ b/zenoh-jni/src/logger.rs @@ -17,34 +17,32 @@ use jni::{ JNIEnv, }; -use crate::{ - errors::{Error, Result}, - jni_error, throw_exception, -}; +use crate::{errors::ZResult, throw_exception, zerror}; /// Redirects the Rust logs either to logcat for Android systems or to the standard output (for non-Android systems). /// -/// This function is meant to be called from Java/Kotlin code through JNI. It takes a `log_level` -/// indicating the desired log level, which must be one of the following: "info", "debug", "warn", -/// "trace", or "error". +/// This function is meant to be called from Java/Kotlin code through JNI. It takes a `filter` +/// indicating the desired log level. +/// +/// See https://docs.rs/env_logger/latest/env_logger/index.html for accepted filter format. /// /// # Parameters: /// - `env`: The JNI environment. /// - `_class`: The JNI class. -/// - `log_level`: The log level java string indicating the desired log level. +/// - `filter`: The logs filter. /// /// # Errors: /// - If there is an error parsing the log level string, a `JNIException` is thrown on the JVM. /// #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_Logger_00024Companion_start( +pub extern "C" fn Java_io_zenoh_Logger_00024Companion_startLogsViaJNI( mut env: JNIEnv, _class: JClass, - log_level: JString, + filter: JString, ) { - || -> Result<()> { - let log_level = parse_log_level(&mut env, log_level)?; + || -> ZResult<()> { + let log_level = parse_filter(&mut env, filter)?; android_logd_logger::builder() .parse_filters(log_level.as_str()) .tag_target_strip() @@ -55,10 +53,10 @@ pub extern "C" fn Java_io_zenoh_Logger_00024Companion_start( .unwrap_or_else(|err| throw_exception!(env, err)) } -fn parse_log_level(env: &mut JNIEnv, log_level: JString) -> Result { - let log_level = env.get_string(&log_level).map_err(|err| jni_error!(err))?; +fn parse_filter(env: &mut JNIEnv, log_level: JString) -> ZResult { + let log_level = env.get_string(&log_level).map_err(|err| zerror!(err))?; log_level .to_str() .map(|level| Ok(level.to_string())) - .map_err(|err| jni_error!(err))? + .map_err(|err| zerror!(err))? } diff --git a/zenoh-jni/src/publisher.rs b/zenoh-jni/src/publisher.rs index c6607cd0..ead60c3f 100644 --- a/zenoh-jni/src/publisher.rs +++ b/zenoh-jni/src/publisher.rs @@ -21,11 +21,12 @@ use jni::{ }; use zenoh::{pubsub::Publisher, Wait}; +use crate::throw_exception; use crate::{ - errors::Result, + errors::ZResult, utils::{decode_byte_array, decode_encoding}, + zerror, }; -use crate::{session_error, throw_exception}; /// Performs a PUT operation on a Zenoh publisher via JNI. /// @@ -56,7 +57,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( publisher_ptr: *const Publisher<'static>, ) { let publisher = Arc::from_raw(publisher_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let payload = decode_byte_array(&env, payload)?; let mut publication = publisher.put(payload); let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; @@ -65,7 +66,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_putViaJNI( let attachment = decode_byte_array(&env, attachment)?; publication = publication.attachment::>(attachment) }; - publication.wait().map_err(|err| session_error!(err)) + publication.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(publisher); @@ -94,13 +95,13 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNIPublisher_deleteViaJNI( publisher_ptr: *const Publisher<'static>, ) { let publisher = Arc::from_raw(publisher_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let mut delete = publisher.delete(); if !attachment.is_null() { let attachment = decode_byte_array(&env, attachment)?; delete = delete.attachment::>(attachment) }; - delete.wait().map_err(|err| session_error!(err)) + delete.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(publisher) diff --git a/zenoh-jni/src/query.rs b/zenoh-jni/src/query.rs index f7b557ec..031b1d93 100644 --- a/zenoh-jni/src/query.rs +++ b/zenoh-jni/src/query.rs @@ -14,11 +14,9 @@ use std::sync::Arc; -use crate::{errors::Result, key_expr::process_kotlin_key_expr, throw_exception}; -use crate::{ - session_error, - utils::{decode_byte_array, decode_encoding}, -}; +use crate::utils::{decode_byte_array, decode_encoding}; +use crate::zerror; +use crate::{errors::ZResult, key_expr::process_kotlin_key_expr, throw_exception}; use jni::{ objects::{JByteArray, JClass, JString}, sys::{jboolean, jint, jlong}, @@ -27,10 +25,10 @@ use jni::{ use uhlc::ID; use zenoh::{ key_expr::KeyExpr, - prelude::Wait, qos::{CongestionControl, Priority}, query::Query, time::{Timestamp, NTP64}, + Wait, }; /// Replies with `success` to a Zenoh [Query] via JNI, freeing the query in the process. @@ -76,7 +74,7 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replySuccessViaJNI( qos_priority: jint, qos_congestion_control: jint, ) { - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let query = Arc::from_raw(query_ptr); let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let payload = decode_byte_array(&env, payload)?; @@ -97,7 +95,7 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replySuccessViaJNI( } else { reply_builder.congestion_control(CongestionControl::Drop) }; - reply_builder.wait().map_err(|err| session_error!(err)) + reply_builder.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); } @@ -129,14 +127,14 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replyErrorViaJNI( encoding_id: jint, encoding_schema: /*nullable*/ JString, ) { - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let query = Arc::from_raw(query_ptr); let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; query .reply_err(decode_byte_array(&env, payload)?) .encoding(encoding) .wait() - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); } @@ -178,7 +176,7 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replyDeleteViaJNI( qos_priority: jint, qos_congestion_control: jint, ) { - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let query = Arc::from_raw(query_ptr); let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let mut reply_builder = query.reply_del(key_expr); @@ -196,7 +194,7 @@ pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQuery_replyDeleteViaJNI( } else { reply_builder.congestion_control(CongestionControl::Drop) }; - reply_builder.wait().map_err(|err| session_error!(err)) + reply_builder.wait().map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); } diff --git a/zenoh-jni/src/queryable.rs b/zenoh-jni/src/queryable.rs index 07ed6e1b..5d2ddb1d 100644 --- a/zenoh-jni/src/queryable.rs +++ b/zenoh-jni/src/queryable.rs @@ -35,7 +35,7 @@ use zenoh::query::Queryable; pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIQueryable_freePtrViaJNI( _env: JNIEnv, _: JClass, - queryable_ptr: *const Queryable<'_, ()>, + queryable_ptr: *const Queryable<()>, ) { Arc::from_raw(queryable_ptr); } diff --git a/zenoh-jni/src/scouting.rs b/zenoh-jni/src/scouting.rs new file mode 100644 index 00000000..b0a665c1 --- /dev/null +++ b/zenoh-jni/src/scouting.rs @@ -0,0 +1,113 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use std::{ptr::null, sync::Arc}; + +use jni::{ + objects::{GlobalRef, JClass, JList, JObject, JValue}, + sys::jint, + JNIEnv, +}; +use zenoh::{config::WhatAmIMatcher, Wait}; +use zenoh::{scouting::Scout, Config}; + +use crate::utils::{get_callback_global_ref, get_java_vm, load_on_close}; +use crate::{errors::ZResult, throw_exception, zerror}; + +/// Start a scout. +/// +/// # Params +/// - `whatAmI`: Ordinal value of the WhatAmI enum. +/// - `callback`: Callback to be executed whenever a hello message is received. +/// - `config_ptr`: Optional config pointer. +/// +/// Returns a pointer to the scout, which must be freed afterwards. +/// If starting the scout fails, an exception is thrown on the JVM, and a null pointer is returned. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIScout_00024Companion_scoutViaJNI( + mut env: JNIEnv, + _class: JClass, + whatAmI: jint, + callback: JObject, + on_close: JObject, + config_ptr: /*nullable=*/ *const Config, +) -> *const Scout<()> { + || -> ZResult<*const Scout<()>> { + let callback_global_ref = get_callback_global_ref(&mut env, callback)?; + let java_vm = Arc::new(get_java_vm(&mut env)?); + let on_close_global_ref: GlobalRef = get_callback_global_ref(&mut env, on_close)?; + let on_close = load_on_close(&java_vm, on_close_global_ref); + let whatAmIMatcher: WhatAmIMatcher = (whatAmI as u8).try_into().unwrap(); // The validity of the operation is guaranteed on the kotlin layer. + let config = if config_ptr.is_null() { + Config::default() + } else { + let arc_cfg = Arc::from_raw(config_ptr); + let config_clone = arc_cfg.as_ref().clone(); + std::mem::forget(arc_cfg); + config_clone + }; + zenoh::scout(whatAmIMatcher, config) + .callback(move |hello| { + on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure + tracing::debug!("Received hello: {hello}"); + let _ = || -> jni::errors::Result<()> { + let mut env = java_vm.attach_current_thread_as_daemon()?; + let whatami = hello.whatami() as jint; + let zenoh_id = env + .byte_array_from_slice(&hello.zid().to_le_bytes()) + .map(|it| env.auto_local(it))?; + let locators = env + .new_object("java/util/ArrayList", "()V", &[]) + .map(|it| env.auto_local(it))?; + let jlist = JList::from_env(&mut env, &locators)?; + for value in hello.locators() { + let locator = env.new_string(value.as_str())?; + jlist.add(&mut env, &locator)?; + } + env.call_method( + &callback_global_ref, + "run", + "(I[BLjava/util/List;)V", + &[ + JValue::from(whatami), + JValue::from(&zenoh_id), + JValue::from(&locators), + ], + )?; + Ok(()) + }() + .map_err(|err| tracing::error!("Error while scouting: ${err}")); + }) + .wait() + .map(|scout| Arc::into_raw(Arc::new(scout))) + .map_err(|err| zerror!(err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + null() + }) +} + +/// Frees the scout. +#[no_mangle] +#[allow(non_snake_case)] +pub(crate) unsafe extern "C" fn Java_io_zenoh_jni_JNIScout_00024Companion_freePtrViaJNI( + _env: JNIEnv, + _: JClass, + scout_ptr: *const Scout<()>, +) { + Arc::from_raw(scout_ptr); +} diff --git a/zenoh-jni/src/session.rs b/zenoh-jni/src/session.rs index 9240d82a..43017c0d 100644 --- a/zenoh-jni/src/session.rs +++ b/zenoh-jni/src/session.rs @@ -12,26 +12,26 @@ // ZettaScale Zenoh Team, // -use crate::errors::{Error, Result}; -use crate::key_expr::process_kotlin_key_expr; -use crate::{jni_error, utils::*}; -use crate::{session_error, throw_exception}; - -use jni::objects::{GlobalRef, JByteArray, JClass, JObject, JString, JValue}; -use jni::sys::{jboolean, jint, jlong}; -use jni::JNIEnv; -use std::mem; -use std::ops::Deref; -use std::ptr::null; -use std::sync::Arc; -use std::time::Duration; -use zenoh::config::{Config, ZenohId}; -use zenoh::key_expr::KeyExpr; -use zenoh::prelude::Wait; -use zenoh::pubsub::{Publisher, Subscriber}; -use zenoh::query::{Query, Queryable, ReplyError, Selector}; -use zenoh::sample::Sample; -use zenoh::session::{Session, SessionDeclarations}; +use std::{mem, ops::Deref, ptr::null, sync::Arc, time::Duration}; + +use jni::{ + objects::{GlobalRef, JByteArray, JClass, JList, JObject, JString, JValue}, + sys::{jboolean, jbyteArray, jint, jlong, jobject}, + JNIEnv, +}; +use zenoh::{ + config::Config, + key_expr::KeyExpr, + pubsub::{Publisher, Subscriber}, + query::{Query, Queryable, ReplyError, Selector}, + sample::Sample, + session::{Session, ZenohId}, + Wait, +}; + +use crate::{ + errors::ZResult, key_expr::process_kotlin_key_expr, throw_exception, utils::*, zerror, +}; /// Open a Zenoh session via JNI. /// @@ -48,17 +48,17 @@ use zenoh::session::{Session, SessionDeclarations}; /// #[no_mangle] #[allow(non_snake_case)] -pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( mut env: JNIEnv, _class: JClass, - config_path: /*nullable*/ JString, + config_ptr: *const Config, ) -> *const Session { - let session = open_session(&mut env, config_path); + let session = open_session(config_ptr); match session { Ok(session) => Arc::into_raw(Arc::new(session)), Err(err) => { tracing::error!("Unable to open session: {}", err); - throw_exception!(env, session_error!(err)); + throw_exception!(env, zerror!(err)); null() } } @@ -68,16 +68,13 @@ pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionViaJNI( /// /// If the config path provided is null then the default configuration is loaded. /// -fn open_session(env: &mut JNIEnv, config_path: JString) -> Result { - let config = if config_path.is_null() { - Config::default() - } else { - let config_file_path = decode_string(env, &config_path)?; - Config::from_file(config_file_path).map_err(|err| session_error!(err))? - }; - zenoh::open(config) +unsafe fn open_session(config_ptr: *const Config) -> ZResult { + let config = Arc::from_raw(config_ptr); + let result = zenoh::open(config.as_ref().clone()) .wait() - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)); + mem::forget(config); + result } /// Open a Zenoh session with a JSON configuration. @@ -91,7 +88,7 @@ fn open_session(env: &mut JNIEnv, config_path: JString) -> Result { /// # Parameters: /// - `env`: The JNI environment. /// - `_class`: The JNI class (parameter required by the JNI interface but unused). -/// - `json_config`: Nullable configuration as a JSON string. If null, the default configuration will be loaded. +/// - `json_config`: Configuration as a JSON string. /// #[no_mangle] #[allow(non_snake_case)] @@ -105,7 +102,7 @@ pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithJsonConfigViaJNI( Ok(session) => Arc::into_raw(Arc::new(session)), Err(err) => { tracing::error!("Unable to open session: {}", err); - throw_exception!(env, session_error!(err)); + throw_exception!(env, zerror!(err)); null() } } @@ -113,23 +110,58 @@ pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithJsonConfigViaJNI( /// Open a Zenoh session with the provided json configuration. /// -/// If the provided json config is null, then the default config is loaded. +fn open_session_with_json_config(env: &mut JNIEnv, json_config: JString) -> ZResult { + let json_config = decode_string(env, &json_config)?; + let mut deserializer = + json5::Deserializer::from_str(&json_config).map_err(|err| zerror!(err))?; + let config = Config::from_deserializer(&mut deserializer).map_err(|err| match err { + Ok(c) => zerror!("Invalid configuration: {}", c), + Err(e) => zerror!("JSON error: {}", e), + })?; + zenoh::open(config).wait().map_err(|err| zerror!(err)) +} + +/// Open a Zenoh session with a YAML configuration. /// -fn open_session_with_json_config(env: &mut JNIEnv, json_config: JString) -> Result { - let config = if json_config.is_null() { - Config::default() - } else { - let json_config = decode_string(env, &json_config)?; - let mut deserializer = - json5::Deserializer::from_str(&json_config).map_err(|err| session_error!(err))?; - Config::from_deserializer(&mut deserializer).map_err(|err| match err { - Ok(c) => session_error!("Invalid configuration: {}", c), - Err(e) => session_error!("JSON error: {}", e), - })? - }; - zenoh::open(config) - .wait() - .map_err(|err| session_error!(err)) +/// It returns an [Arc] raw pointer to the Zenoh Session, which should be stored as a private read-only attribute +/// of the session object in the Java/Kotlin code. Subsequent calls to other session functions will require +/// this raw pointer to retrieve the [Session] using `Arc::from_raw`. +/// +/// If opening the session fails, an exception is thrown on the JVM, and a null pointer is returned. +/// +/// # Parameters: +/// - `env`: The JNI environment. +/// - `_class`: The JNI class (parameter required by the JNI interface but unused). +/// - `yaml_config`: Configuration as a YAML string. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNISession_openSessionWithYamlConfigViaJNI( + mut env: JNIEnv, + _class: JClass, + yaml_config: JString, +) -> *const Session { + let session = open_session_with_yaml_config(&mut env, yaml_config); + match session { + Ok(session) => Arc::into_raw(Arc::new(session)), + Err(err) => { + tracing::error!("Unable to open session: {}", err); + throw_exception!(env, zerror!(err)); + null() + } + } +} + +/// Open a Zenoh session with the provided yaml configuration. +/// +fn open_session_with_yaml_config(env: &mut JNIEnv, yaml_config: JString) -> ZResult { + let yaml_config = decode_string(env, &yaml_config)?; + let deserializer = serde_yaml::Deserializer::from_str(&yaml_config); + let config = Config::from_deserializer(deserializer).map_err(|err| match err { + Ok(c) => zerror!("Invalid configuration: {}", c), + Err(e) => zerror!("YAML error: {}", e), + })?; + zenoh::open(config).wait().map_err(|err| zerror!(err)) } /// Closes a Zenoh session via JNI. @@ -152,21 +184,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_closeSessionViaJNI( _class: JClass, session_ptr: *const Session, ) { - let ptr = Arc::try_unwrap(Arc::from_raw(session_ptr)); - match ptr { - Ok(session) => { - // Do nothing, the pointer will be freed. - } - Err(arc_session) => { - let ref_count = Arc::strong_count(&arc_session); - throw_exception!(env, session_error!( - "Attempted to close the session, but at least one strong reference to it is still alive - (ref count: {}). All the declared publishers, subscribers, and queryables need to be - dropped first.", - ref_count - )); - } - }; + Arc::from_raw(session_ptr); } /// Declare a Zenoh publisher via JNI. @@ -182,6 +200,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_closeSessionViaJNI( /// - `congestion_control`: The [zenoh::publisher::CongestionControl] configuration as an ordinal. /// - `priority`: The [zenoh::core::Priority] configuration as an ordinal. /// - `is_express`: The express config of the publisher (see [zenoh::prelude::QoSBuilderTrait]). +/// - `reliability`: The reliability value as an ordinal. /// /// # Returns: /// - A raw pointer to the declared Zenoh publisher or null in case of failure. @@ -204,21 +223,24 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( congestion_control: jint, priority: jint, is_express: jboolean, + reliability: jint, ) -> *const Publisher<'static> { let session = Arc::from_raw(session_ptr); - let publisher_ptr = || -> Result<*const Publisher<'static>> { + let publisher_ptr = || -> ZResult<*const Publisher<'static>> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let congestion_control = decode_congestion_control(congestion_control)?; let priority = decode_priority(priority)?; + let reliability = decode_reliability(reliability)?; let result = session .declare_publisher(key_expr) .congestion_control(congestion_control) .priority(priority) .express(is_express != 0) + .reliability(reliability) .wait(); match result { Ok(publisher) => Ok(Arc::into_raw(Arc::new(publisher))), - Err(err) => Err(session_error!(err)), + Err(err) => Err(zerror!(err)), } }() .unwrap_or_else(|err| { @@ -246,6 +268,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declarePublisherViaJNI( /// - `priority`: The [Priority] mechanism specified. /// - `is_express`: The express flag. /// - `attachment`: Optional attachment encoded into a byte array. May be null. +/// - `reliability`: The reliability value as an ordinal. /// /// Safety: /// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. @@ -269,21 +292,24 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( priority: jint, is_express: jboolean, attachment: JByteArray, + reliability: jint, ) { let session = Arc::from_raw(session_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let payload = decode_byte_array(&env, payload)?; let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; let congestion_control = decode_congestion_control(congestion_control)?; let priority = decode_priority(priority)?; + let reliability = decode_reliability(reliability)?; let mut put_builder = session .put(&key_expr, payload) .congestion_control(congestion_control) .encoding(encoding) .express(is_express != 0) - .priority(priority); + .priority(priority) + .reliability(reliability); if !attachment.is_null() { let attachment = decode_byte_array(&env, attachment)?; @@ -293,7 +319,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( put_builder .wait() .map(|_| tracing::trace!("Put on '{key_expr}'")) - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(session); @@ -313,6 +339,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_putViaJNI( /// - `priority`: The [Priority] mechanism specified. /// - `is_express`: The express flag. /// - `attachment`: Optional attachment encoded into a byte array. May be null. +/// - `reliability`: The reliability value as an ordinal. /// /// Safety: /// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. @@ -334,18 +361,21 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( priority: jint, is_express: jboolean, attachment: JByteArray, + reliability: jint, ) { let session = Arc::from_raw(session_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let congestion_control = decode_congestion_control(congestion_control)?; let priority = decode_priority(priority)?; + let reliability = decode_reliability(reliability)?; let mut delete_builder = session .delete(&key_expr) .congestion_control(congestion_control) .express(is_express != 0) - .priority(priority); + .priority(priority) + .reliability(reliability); if !attachment.is_null() { let attachment = decode_byte_array(&env, attachment)?; @@ -355,7 +385,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( delete_builder .wait() .map(|_| tracing::trace!("Delete on '{key_expr}'")) - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(session); @@ -373,7 +403,6 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_deleteViaJNI( /// - `session_ptr`: The raw pointer to the Zenoh session. /// - `callback`: The callback function as an instance of the `JNISubscriberCallback` interface in Java/Kotlin. /// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called upon closing the subscriber. -/// - `reliability`: The reliability value as an ordinal. /// /// Returns: /// - A raw pointer to the declared Zenoh subscriber. In case of failure, an exception is thrown and null is returned. @@ -397,14 +426,12 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( session_ptr: *const Session, callback: JObject, on_close: JObject, - reliability: jint, -) -> *const Subscriber<'static, ()> { +) -> *const Subscriber<()> { let session = Arc::from_raw(session_ptr); - || -> Result<*const Subscriber<'static, ()>> { + || -> ZResult<*const Subscriber<()>> { let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; - let reliability = decode_reliability(reliability)?; let on_close = load_on_close(&java_vm, on_close_global_ref); let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; @@ -412,11 +439,11 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( let result = session .declare_subscriber(key_expr.to_owned()) - .callback(move |sample| { + .callback(move |sample: Sample| { on_close.noop(); // Moves `on_close` inside the closure so it gets destroyed with the closure - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - jni_error!("Unable to attach thread for subscriber: {}", err) + zerror!("Unable to attach thread for subscriber: {}", err) })?; let byte_array = bytes_to_java_array(&env, sample.payload()) .map(|array| env.auto_local(array))?; @@ -439,12 +466,12 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( |attachment| bytes_to_java_array(&env, attachment), ) .map(|array| env.auto_local(array)) - .map_err(|err| jni_error!("Error processing attachment: {}", err))?; + .map_err(|err| zerror!("Error processing attachment: {}", err))?; - let key_expr_str = - env.auto_local(env.new_string(sample.key_expr().to_string()).map_err( - |err| jni_error!("Error processing sample key expr: {}", err), - )?); + let key_expr_str = env.auto_local( + env.new_string(sample.key_expr().to_string()) + .map_err(|err| zerror!("Error processing sample key expr: {}", err))?, + ); let express = sample.express(); let priority = sample.priority() as jint; @@ -468,22 +495,16 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareSubscriberViaJNI( JValue::from(cc), ], ) - .map_err(|err| jni_error!(err))?; + .map_err(|err| zerror!(err))?; Ok(()) }() .map_err(|err| tracing::error!("On subscriber callback error: {err}")); }) - .reliability(reliability) .wait(); - let subscriber = - result.map_err(|err| session_error!("Unable to declare subscriber: {}", err))?; + let subscriber = result.map_err(|err| zerror!("Unable to declare subscriber: {}", err))?; - tracing::debug!( - "Subscriber declared on '{}' with reliability '{:?}'.", - key_expr, - reliability - ); + tracing::debug!("Subscriber declared on '{}'.", key_expr); std::mem::forget(session); Ok(Arc::into_raw(Arc::new(subscriber))) }() @@ -532,9 +553,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( callback: JObject, on_close: JObject, complete: jboolean, -) -> *const Queryable<'static, ()> { +) -> *const Queryable<()> { let session = Arc::from_raw(session_ptr); - let query_ptr = || -> Result<*const Queryable<'static, ()>> { + let query_ptr = || -> ZResult<*const Queryable<()>> { let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; @@ -544,7 +565,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( tracing::debug!("Declaring queryable through JNI on {}", key_expr); let builder = session .declare_queryable(key_expr) - .callback(move |query| { + .callback(move |query: Query| { on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure let env = match java_vm.attach_current_thread_as_daemon() { Ok(env) => env, @@ -564,7 +585,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( let queryable = builder .wait() - .map_err(|err| session_error!("Error declaring queryable: {}", err))?; + .map_err(|err| zerror!("Error declaring queryable: {}", err))?; Ok(Arc::into_raw(Arc::new(queryable))) }() .unwrap_or_else(|err| { @@ -575,19 +596,18 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareQueryableViaJNI( query_ptr } -fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> Result<()> { +fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> ZResult<()> { let selector_params_jstr = env .new_string(query.parameters().to_string()) .map(|value| env.auto_local(value)) .map_err(|err| { - jni_error!( + zerror!( "Could not create a JString through JNI for the Query key expression. {}", err ) })?; - let (with_value, payload, encoding_id, encoding_schema) = if let Some(payload) = query.payload() - { + let (payload, encoding_id, encoding_schema) = if let Some(payload) = query.payload() { let encoding = query.encoding().unwrap(); //If there is payload, there is encoding. let encoding_id = encoding.id() as jint; let encoding_schema = encoding @@ -598,10 +618,9 @@ fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> R ) .map(|value| env.auto_local(value))?; let byte_array = bytes_to_java_array(&env, payload).map(|value| env.auto_local(value))?; - (true, byte_array, encoding_id, encoding_schema) + (byte_array, encoding_id, encoding_schema) } else { ( - false, env.auto_local(JByteArray::default()), 0, env.auto_local(JString::default()), @@ -615,13 +634,13 @@ fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> R |attachment| bytes_to_java_array(&env, attachment), ) .map(|value| env.auto_local(value)) - .map_err(|err| jni_error!("Error processing attachment of reply: {}.", err))?; + .map_err(|err| zerror!("Error processing attachment of reply: {}.", err))?; let key_expr_str = env .new_string(&query.key_expr().to_string()) .map(|key_expr| env.auto_local(key_expr)) .map_err(|err| { - jni_error!( + zerror!( "Could not create a JString through JNI for the Query key expression: {}.", err ) @@ -633,11 +652,10 @@ fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> R .call_method( callback_global_ref, "run", - "(Ljava/lang/String;Ljava/lang/String;Z[BILjava/lang/String;[BJ)V", + "(Ljava/lang/String;Ljava/lang/String;[BILjava/lang/String;[BJ)V", &[ JValue::from(&key_expr_str), JValue::from(&selector_params_jstr), - JValue::from(with_value), JValue::from(&payload), JValue::from(encoding_id), JValue::from(&encoding_schema), @@ -655,7 +673,7 @@ fn on_query(mut env: JNIEnv, query: Query, callback_global_ref: &GlobalRef) -> R Arc::from_raw(query_ptr); }; _ = env.exception_describe(); - jni_error!(err) + zerror!(err) }); result } @@ -687,13 +705,13 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_declareKeyExprViaJNI( key_expr_str: JString, ) -> *const KeyExpr<'static> { let session: Arc = Arc::from_raw(session_ptr); - let key_expr_ptr = || -> Result<*const KeyExpr<'static>> { + let key_expr_ptr = || -> ZResult<*const KeyExpr<'static>> { let key_expr_str = decode_string(&mut env, &key_expr_str)?; let key_expr = session .declare_keyexpr(key_expr_str.to_owned()) .wait() .map_err(|err| { - session_error!( + zerror!( "Unable to declare key expression '{}': {}", key_expr_str, err @@ -745,7 +763,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( Err(err) => { throw_exception!( env, - session_error!("Unable to declare key expression '{}': {}", key_expr, err) + zerror!("Unable to declare key expression '{}': {}", key_expr, err) ); } } @@ -762,7 +780,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( /// of using a non declared key expression, in which case the `key_expr_str` parameter will be used instead. /// - `key_expr_str`: String representation of the key expression to be used to declare the query. It is not /// considered if a `key_expr_ptr` is provided. -/// - `selector_params`: Parameters of the selector. +/// - `selector_params`: Optional parameters of the selector. /// - `session_ptr`: A raw pointer to the Zenoh [Session]. /// - `callback`: A Java/Kotlin callback to be called upon receiving a reply. /// - `on_close`: A Java/Kotlin `JNIOnCloseCallback` function interface to be called when no more replies will be received. @@ -770,11 +788,9 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_undeclareKeyExprViaJNI( /// - `target`: The query target as the ordinal of the enum. /// - `consolidation`: The consolidation mode as the ordinal of the enum. /// - `attachment`: An optional attachment encoded into a byte array. -/// - `with_value`: Boolean value to tell if a value must be included in the get operation. If true, -/// then the next params are valid. -/// - `payload`: The payload of the value. -/// - `encoding_id`: The encoding of the value payload. -/// - `encoding_schema`: The encoding schema of the value payload, may be null. +/// - `payload`: Optional payload for the query. +/// - `encoding_id`: The encoding of the payload. +/// - `encoding_schema`: The encoding schema of the payload, may be null. /// /// Safety: /// - The function is marked as unsafe due to raw pointer manipulation and JNI interaction. @@ -793,7 +809,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( _class: JClass, key_expr_ptr: /*nullable*/ *const KeyExpr<'static>, key_expr_str: JString, - selector_params: JString, + selector_params: /*nullable*/ JString, session_ptr: *const Session, callback: JObject, on_close: JObject, @@ -801,31 +817,34 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( target: jint, consolidation: jint, attachment: /*nullable*/ JByteArray, - with_value: jboolean, payload: /*nullable*/ JByteArray, encoding_id: jint, encoding_schema: /*nullable*/ JString, ) { let session = Arc::from_raw(session_ptr); - let _ = || -> Result<()> { + let _ = || -> ZResult<()> { let key_expr = process_kotlin_key_expr(&mut env, &key_expr_str, key_expr_ptr)?; let java_vm = Arc::new(get_java_vm(&mut env)?); let callback_global_ref = get_callback_global_ref(&mut env, callback)?; let on_close_global_ref = get_callback_global_ref(&mut env, on_close)?; let query_target = decode_query_target(target)?; let consolidation = decode_consolidation(consolidation)?; - let selector_params = decode_string(&mut env, &selector_params)?; let timeout = Duration::from_millis(timeout_ms as u64); let on_close = load_on_close(&java_vm, on_close_global_ref); - let selector = Selector::owned(&key_expr, &*selector_params); + let selector_params = if selector_params.is_null() { + String::new() + } else { + decode_string(&mut env, &selector_params)? + }; + let selector = Selector::owned(&key_expr, selector_params); let mut get_builder = session .get(selector) .callback(move |reply| { - || -> Result<()> { + || -> ZResult<()> { on_close.noop(); // Does nothing, but moves `on_close` inside the closure so it gets destroyed with the closure tracing::debug!("Receiving reply through JNI: {:?}", reply); let mut env = java_vm.attach_current_thread_as_daemon().map_err(|err| { - jni_error!("Unable to attach thread for GET query callback: {}.", err) + zerror!("Unable to attach thread for GET query callback: {}.", err) })?; match reply.result() { @@ -849,7 +868,7 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( .timeout(timeout) .consolidation(consolidation); - if with_value != 0 { + if !payload.is_null() { let encoding = decode_encoding(&mut env, encoding_id, &encoding_schema)?; get_builder = get_builder.encoding(encoding); get_builder = get_builder.payload(decode_byte_array(&env, payload)?); @@ -863,24 +882,24 @@ pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getViaJNI( get_builder .wait() .map(|_| tracing::trace!("Performing get on '{key_expr}'.",)) - .map_err(|err| session_error!(err)) + .map_err(|err| zerror!(err)) }() .map_err(|err| throw_exception!(env, err)); std::mem::forget(session); } -fn on_reply_success( +pub(crate) fn on_reply_success( env: &mut JNIEnv, replier_id: Option, sample: &Sample, callback_global_ref: &GlobalRef, -) -> Result<()> { +) -> ZResult<()> { let zenoh_id = replier_id .map_or_else( - || Ok(JString::default()), + || Ok(JByteArray::default()), |replier_id| { - env.new_string(replier_id.to_string()) - .map_err(|err| jni_error!(err)) + env.byte_array_from_slice(&replier_id.to_le_bytes()) + .map_err(|err| zerror!(err)) }, ) .map(|value| env.auto_local(value))?; @@ -910,13 +929,13 @@ fn on_reply_success( |attachment| bytes_to_java_array(env, attachment), ) .map(|value| env.auto_local(value)) - .map_err(|err| jni_error!("Error processing attachment of reply: {}.", err))?; + .map_err(|err| zerror!("Error processing attachment of reply: {}.", err))?; let key_expr_str = env .new_string(sample.key_expr().to_string()) .map(|value| env.auto_local(value)) .map_err(|err| { - jni_error!( + zerror!( "Could not create a JString through JNI for the Sample key expression. {}", err ) @@ -929,7 +948,7 @@ fn on_reply_success( let result = match env.call_method( callback_global_ref, "run", - "(Ljava/lang/String;ZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", + "([BZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", &[ JValue::from(&zenoh_id), JValue::from(true), @@ -949,24 +968,24 @@ fn on_reply_success( Ok(_) => Ok(()), Err(err) => { _ = env.exception_describe(); - Err(jni_error!("On GET callback error: {}", err)) + Err(zerror!("On GET callback error: {}", err)) } }; result } -fn on_reply_error( +pub(crate) fn on_reply_error( env: &mut JNIEnv, replier_id: Option, reply_error: &ReplyError, callback_global_ref: &GlobalRef, -) -> Result<()> { +) -> ZResult<()> { let zenoh_id = replier_id .map_or_else( - || Ok(JString::default()), + || Ok(JByteArray::default()), |replier_id| { - env.new_string(replier_id.to_string()) - .map_err(|err| jni_error!(err)) + env.byte_array_from_slice(&replier_id.to_le_bytes()) + .map_err(|err| zerror!(err)) }, ) .map(|value| env.auto_local(value))?; @@ -985,7 +1004,7 @@ fn on_reply_error( let result = match env.call_method( callback_global_ref, "run", - "(Ljava/lang/String;ZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", + "([BZLjava/lang/String;[BILjava/lang/String;IJZ[BZII)V", &[ JValue::from(&zenoh_id), JValue::from(false), @@ -1006,8 +1025,87 @@ fn on_reply_error( Ok(_) => Ok(()), Err(err) => { _ = env.exception_describe(); - Err(jni_error!("On GET callback error: {}", err)) + Err(zerror!("On GET callback error: {}", err)) } }; result } + +/// Returns a list of zenoh ids as byte arrays corresponding to the peers connected to the session provided. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getPeersZidViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, +) -> jobject { + let session = Arc::from_raw(session_ptr); + let ids = { + let peers_zid = session.info().peers_zid().wait(); + let ids = peers_zid.collect::>(); + ids_to_java_list(&mut env, ids).map_err(|err| zerror!(err)) + } + .unwrap_or_else(|err| { + throw_exception!(env, err); + JObject::default().as_raw() + }); + std::mem::forget(session); + ids +} + +/// Returns a list of zenoh ids as byte arrays corresponding to the routers connected to the session provided. +/// +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getRoutersZidViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, +) -> jobject { + let session = Arc::from_raw(session_ptr); + let ids = { + let peers_zid = session.info().routers_zid().wait(); + let ids = peers_zid.collect::>(); + ids_to_java_list(&mut env, ids).map_err(|err| zerror!(err)) + } + .unwrap_or_else(|err| { + throw_exception!(env, err); + JObject::default().as_raw() + }); + std::mem::forget(session); + ids +} + +/// Returns the Zenoh ID as a byte array of the session. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNISession_getZidViaJNI( + mut env: JNIEnv, + _class: JClass, + session_ptr: *const Session, +) -> jbyteArray { + let session = Arc::from_raw(session_ptr); + let ids = { + let zid = session.info().zid().wait(); + env.byte_array_from_slice(&zid.to_le_bytes()) + .map(|x| x.as_raw()) + .map_err(|err| zerror!(err)) + } + .unwrap_or_else(|err| { + throw_exception!(env, err); + JByteArray::default().as_raw() + }); + std::mem::forget(session); + ids +} + +fn ids_to_java_list(env: &mut JNIEnv, ids: Vec) -> jni::errors::Result { + let array_list = env.new_object("java/util/ArrayList", "()V", &[])?; + let jlist = JList::from_env(env, &array_list)?; + for id in ids { + let value = &mut env.byte_array_from_slice(&id.to_le_bytes())?; + jlist.add(env, value)?; + } + Ok(array_list.as_raw()) +} diff --git a/zenoh-jni/src/utils.rs b/zenoh-jni/src/utils.rs index 96c705f9..aa8d0176 100644 --- a/zenoh-jni/src/utils.rs +++ b/zenoh-jni/src/utils.rs @@ -14,10 +14,7 @@ use std::sync::Arc; -use crate::{ - errors::{Error, Result}, - jni_error, session_error, throw_exception, -}; +use crate::{errors::ZResult, throw_exception, zerror}; use jni::{ objects::{JByteArray, JObject, JString}, sys::jint, @@ -26,19 +23,18 @@ use jni::{ use zenoh::{ bytes::{Encoding, ZBytes}, internal::buffers::ZSlice, - pubsub::Reliability, - qos::{CongestionControl, Priority}, + qos::{CongestionControl, Priority, Reliability}, query::{ConsolidationMode, QueryTarget}, }; /// Converts a JString into a rust String. -pub(crate) fn decode_string(env: &mut JNIEnv, string: &JString) -> Result { +pub(crate) fn decode_string(env: &mut JNIEnv, string: &JString) -> ZResult { let binding = env .get_string(string) - .map_err(|err| jni_error!("Error while retrieving JString: {}", err))?; + .map_err(|err| zerror!("Error while retrieving JString: {}", err))?; let value = binding .to_str() - .map_err(|err| jni_error!("Error decoding JString: {}", err))?; + .map_err(|err| zerror!("Error decoding JString: {}", err))?; Ok(value.to_string()) } @@ -46,99 +42,93 @@ pub(crate) fn decode_encoding( env: &mut JNIEnv, encoding: jint, schema: &JString, -) -> Result { +) -> ZResult { let schema: Option = if schema.is_null() { None } else { Some(decode_string(env, schema)?.into_bytes().into()) }; let encoding_id = - u16::try_from(encoding).map_err(|err| jni_error!("Failed to decode encoding: {}", err))?; + u16::try_from(encoding).map_err(|err| zerror!("Failed to decode encoding: {}", err))?; Ok(Encoding::new(encoding_id, schema)) } -pub(crate) fn get_java_vm(env: &mut JNIEnv) -> Result { +pub(crate) fn get_java_vm(env: &mut JNIEnv) -> ZResult { env.get_java_vm() - .map_err(|err| jni_error!("Unable to retrieve JVM reference: {}", err)) + .map_err(|err| zerror!("Unable to retrieve JVM reference: {}", err)) } pub(crate) fn get_callback_global_ref( env: &mut JNIEnv, callback: JObject, -) -> crate::errors::Result { +) -> crate::errors::ZResult { env.new_global_ref(callback) - .map_err(|err| jni_error!("Unable to get reference to the provided callback: {}", err)) + .map_err(|err| zerror!("Unable to get reference to the provided callback: {}", err)) } /// Helper function to convert a JByteArray into a Vec. -pub(crate) fn decode_byte_array(env: &JNIEnv<'_>, payload: JByteArray) -> Result> { +pub(crate) fn decode_byte_array(env: &JNIEnv<'_>, payload: JByteArray) -> ZResult> { let payload_len = env .get_array_length(&payload) .map(|length| length as usize) - .map_err(|err| jni_error!(err))?; + .map_err(|err| zerror!(err))?; let mut buff = vec![0; payload_len]; env.get_byte_array_region(payload, 0, &mut buff[..]) - .map_err(|err| jni_error!(err))?; + .map_err(|err| zerror!(err))?; let buff: Vec = unsafe { std::mem::transmute::, Vec>(buff) }; Ok(buff) } -pub(crate) fn decode_priority(priority: jint) -> Result { - Priority::try_from(priority as u8) - .map_err(|err| session_error!("Error retrieving priority: {}.", err)) +pub(crate) fn decode_priority(priority: jint) -> ZResult { + Priority::try_from(priority as u8).map_err(|err| zerror!("Error retrieving priority: {}.", err)) } -pub(crate) fn decode_congestion_control(congestion_control: jint) -> Result { +pub(crate) fn decode_congestion_control(congestion_control: jint) -> ZResult { match congestion_control { 1 => Ok(CongestionControl::Block), 0 => Ok(CongestionControl::Drop), - value => Err(session_error!("Unknown congestion control '{}'.", value)), + value => Err(zerror!("Unknown congestion control '{}'.", value)), } } -pub(crate) fn decode_query_target(target: jint) -> Result { +pub(crate) fn decode_query_target(target: jint) -> ZResult { match target { 0 => Ok(QueryTarget::BestMatching), 1 => Ok(QueryTarget::All), 2 => Ok(QueryTarget::AllComplete), - value => Err(session_error!("Unable to decode QueryTarget '{}'.", value)), + value => Err(zerror!("Unable to decode QueryTarget '{}'.", value)), } } -pub(crate) fn decode_consolidation(consolidation: jint) -> Result { +pub(crate) fn decode_consolidation(consolidation: jint) -> ZResult { match consolidation { 0 => Ok(ConsolidationMode::Auto), 1 => Ok(ConsolidationMode::None), 2 => Ok(ConsolidationMode::Monotonic), 3 => Ok(ConsolidationMode::Latest), - value => Err(session_error!("Unable to decode consolidation '{}'", value)), + value => Err(zerror!("Unable to decode consolidation '{}'", value)), } } -pub(crate) fn decode_reliability(reliability: jint) -> Result { +pub(crate) fn decode_reliability(reliability: jint) -> ZResult { match reliability { 0 => Ok(Reliability::BestEffort), 1 => Ok(Reliability::Reliable), - value => Err(session_error!("Unable to decode reliability '{}'", value)), + value => Err(zerror!("Unable to decode reliability '{}'", value)), } } -pub(crate) fn bytes_to_java_array<'a>(env: &JNIEnv<'a>, slice: &ZBytes) -> Result> { - env.byte_array_from_slice( - slice - .deserialize::>() - .map_err(|err| session_error!("Unable to deserialize slice: {}", err))? - .as_ref(), - ) - .map_err(|err| jni_error!(err)) +pub(crate) fn bytes_to_java_array<'a>(env: &JNIEnv<'a>, slice: &ZBytes) -> ZResult> { + env.byte_array_from_slice(&slice.to_bytes()) + .map_err(|err| zerror!(err)) } -pub(crate) fn slice_to_java_string<'a>(env: &JNIEnv<'a>, slice: &ZSlice) -> Result> { +pub(crate) fn slice_to_java_string<'a>(env: &JNIEnv<'a>, slice: &ZSlice) -> ZResult> { env.new_string( String::from_utf8(slice.to_vec()) - .map_err(|err| session_error!("Unable to decode string: {}", err))?, + .map_err(|err| zerror!("Unable to decode string: {}", err))?, ) - .map_err(|err| jni_error!(err)) + .map_err(|err| zerror!(err)) } /// A type that calls a function when dropped @@ -182,7 +172,7 @@ pub(crate) fn load_on_close( _ = env.exception_describe(); throw_exception!( env, - jni_error!("Error while running 'onClose' callback: {}", err) + zerror!("Error while running 'onClose' callback: {}", err) ); } } diff --git a/zenoh-jni/src/zenoh_id.rs b/zenoh-jni/src/zenoh_id.rs new file mode 100644 index 00000000..6647f86f --- /dev/null +++ b/zenoh-jni/src/zenoh_id.rs @@ -0,0 +1,42 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use crate::{errors::ZResult, throw_exception, utils::decode_byte_array, zerror}; +use jni::{ + objects::{JByteArray, JClass, JString}, + sys::jstring, + JNIEnv, +}; +use zenoh::session::ZenohId; + +/// Returns the string representation of a ZenohID. +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn Java_io_zenoh_jni_JNIZenohId_toStringViaJNI( + mut env: JNIEnv, + _class: JClass, + zenoh_id: JByteArray, +) -> jstring { + || -> ZResult { + let bytes = decode_byte_array(&env, zenoh_id)?; + let zenohid = ZenohId::try_from(bytes.as_slice()).map_err(|err| zerror!(err))?; + env.new_string(zenohid.to_string()) + .map_err(|err| zerror!(err)) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default() + }) + .as_raw() +}