diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 00a51aff..00000000 --- a/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -# -# https://help.github.com/articles/dealing-with-line-endings/ -# -# These are explicitly windows files and should use crlf -*.bat text eol=crlf - diff --git a/.github/workflows/bump.yaml b/.github/workflows/bump.yaml deleted file mode 100644 index 1e4ce481..00000000 --- a/.github/workflows/bump.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: Bump version -on: - workflow_dispatch: - inputs: - version: - description: 'Version to bump (without prepending "v")' - required: true - -jobs: - bump: - name: Bump release version - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install dasel - run: curl -sSLf "$(curl -sSLf https://api.github.com/repos/tomwright/dasel/releases/latest | grep browser_download_url | grep linux_amd64 | grep -v .gz | cut -d\" -f 4)" -L -o dasel && chmod +x dasel && mv ./dasel /usr/local/bin/dasel - - name: Bump version overwriting libs.versions.toml - run: dasel -f gradle/libs.versions.toml put -t string -v "${{ github.event.inputs.version }}" ".versions.restate" - - name: Create version bump PR - uses: peter-evans/create-pull-request@v3 - with: - title: "[Release] Bump to ${{ github.event.inputs.version }}" - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "Bump to ${{ github.event.inputs.version }}" - signoff: true - branch: "bump/${{ github.event.inputs.version }}" - body: > - This PR performs the bump of the SDK to ${{ github.event.inputs.version }}. - This PR is auto-generated by - [create-pull-request](https://github.com/peter-evans/create-pull-request). \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 02f086d1..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: CI - -on: - pull_request: - workflow_dispatch: - push: - branches: - - main - -jobs: - build-and-test: - name: Build and test (Java ${{ matrix.java }}) - runs-on: ubuntu-latest - timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - java: [ 11 ] - steps: - - uses: actions/checkout@v3 - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 - with: - java-version: ${{ matrix.java }} - distribution: 'adopt' - - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 - - - name: Build with Gradle - uses: gradle/gradle-build-action@v2 - with: - arguments: build - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: Test results (Java ${{ matrix.java }}) - path: "**/test-results/test/*.xml" - - event_file: - name: "Event File" - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Upload - uses: actions/upload-artifact@v2 - with: - name: Event File - path: ${{ github.event_path }} - - e2e: - permissions: - contents: read - issues: read - checks: write - pull-requests: write - actions: read - secrets: inherit - uses: restatedev/e2e/.github/workflows/e2e.yaml@main - with: - sdkJavaCommit: ${{ github.event.pull_request.head.sha || github.sha }} - e2eRef: main diff --git a/sdk-core/src/main/service-protocol/.github/workflows/lint.yaml b/.github/workflows/lint.yaml similarity index 100% rename from sdk-core/src/main/service-protocol/.github/workflows/lint.yaml rename to .github/workflows/lint.yaml diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml deleted file mode 100644 index 2820da9d..00000000 --- a/.github/workflows/manual-release.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Trigger publish manually - -on: - workflow_dispatch: - -jobs: - publish: - uses: ./.github/workflows/release.yml - secrets: inherit diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml deleted file mode 100644 index e1cc04ef..00000000 --- a/.github/workflows/publish-test-results.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Publish test results - -on: - workflow_run: - workflows: [ "CI" ] - types: - - completed - -jobs: - publish-test-results: - name: Publish test results - runs-on: ubuntu-latest - timeout-minutes: 10 - if: github.event.workflow_run.conclusion != 'skipped' - - steps: - - name: Download and Extract Artifacts - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - run: | - mkdir -p artifacts && cd artifacts - - artifacts_url=${{ github.event.workflow_run.artifacts_url }} - - gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact - do - IFS=$'\t' read name url <<< "$artifact" - gh api $url > "$name.zip" - unzip -d "$name" "$name.zip" - done - - - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v1 - with: - commit: ${{ github.event.workflow_run.head_sha }} - event_file: artifacts/Event File/event.json - event_name: ${{ github.event.workflow_run.event }} - files: "artifacts/**/test-results/test/*.xml" \ No newline at end of file diff --git a/.github/workflows/push-release.yml b/.github/workflows/push-release.yml deleted file mode 100644 index f56ce970..00000000 --- a/.github/workflows/push-release.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Trigger release - -on: - workflow_dispatch: - push: - branches: - - main - - -jobs: - publish-to-maven-central: - uses: ./.github/workflows/release.yml - secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 522c1d05..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Publish release - -on: - workflow_call: - -jobs: - publish: - if: github.repository == 'restatedev/sdk-java' - name: Publish - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v3 - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 - with: - java-version: 11 - distribution: 'adopt' - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 - - # Retrieve the version of the SDK - - name: Install dasel - run: curl -sSLf "$(curl -sSLf https://api.github.com/repos/tomwright/dasel/releases/latest | grep browser_download_url | grep linux_amd64 | grep -v .gz | cut -d\" -f 4)" -L -o dasel && chmod +x dasel && mv ./dasel /usr/local/bin/dasel - - name: Parse published sdk version - run: | - echo "PUBLISHED_SDK_VERSION=$(dasel -f gradle/libs.versions.toml .versions.restate)" >> "$GITHUB_ENV" - - # Dry run - - name: Publish dry-run - uses: gradle/gradle-build-action@v2 - env: - # Used for checking the signing - MAVEN_CENTRAL_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_CENTRAL_GPG_PRIVATE_KEY }} - MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.MAVEN_CENTRAL_GPG_PASSPHRASE }} - with: - arguments: publishToMavenLocal - - - name: Publish to staging area on Maven Central - uses: gradle/gradle-build-action@v2 - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - MAVEN_CENTRAL_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_CENTRAL_GPG_PRIVATE_KEY }} - MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.MAVEN_CENTRAL_GPG_PASSPHRASE }} - with: - arguments: publishToSonatype - - - name: Release staging area - uses: gradle/gradle-build-action@v2 - if: "!contains(env.PUBLISHED_SDK_VERSION, '-SNAPSHOT')" - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - MAVEN_CENTRAL_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_CENTRAL_GPG_PRIVATE_KEY }} - MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.MAVEN_CENTRAL_GPG_PASSPHRASE }} - with: - arguments: findSonatypeStagingRepository closeAndReleaseSonatypeStagingRepository diff --git a/.gitignore b/.gitignore index c93860a1..29b636a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,2 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -# Ignore Gradle project-specific cache directory -.gradle - -# Ignore Gradle build output directory -build - .idea -*.iml -.settings -.project -.classpath -.factorypath -kls_database.db +*.iml \ No newline at end of file diff --git a/.ignore b/.ignore deleted file mode 120000 index 3e4e48b0..00000000 --- a/.ignore +++ /dev/null @@ -1 +0,0 @@ -.gitignore \ No newline at end of file diff --git a/sdk-core/src/main/service-protocol/.prettierrc.toml b/.prettierrc.toml similarity index 100% rename from sdk-core/src/main/service-protocol/.prettierrc.toml rename to .prettierrc.toml diff --git a/sdk-core/src/main/service-protocol/.protolint.yaml b/.protolint.yaml similarity index 100% rename from sdk-core/src/main/service-protocol/.protolint.yaml rename to .protolint.yaml diff --git a/LICENSE b/LICENSE index 8963cdde..b81eecf5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ - MIT License +MIT License - Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +Copyright (c) 2023 - Restate Software, Inc., Restate GmbH - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE \ No newline at end of file diff --git a/README.md b/README.md index faf72b98..9ff013d0 100644 --- a/README.md +++ b/README.md @@ -1,444 +1,13 @@ -[![Documentation](https://img.shields.io/badge/doc-reference-blue)](https://docs.restate.dev) -[![javadoc](https://javadoc.io/badge2/dev.restate/sdk-api/javadoc.svg)](https://javadoc.io/doc/dev.restate) -[![Examples](https://img.shields.io/badge/view-examples-blue)](https://github.com/restatedev/examples) -[![Discord](https://img.shields.io/discord/1128210118216007792?logo=discord)](https://discord.gg/skW3AZ6uGd) -[![Twitter](https://img.shields.io/twitter/follow/restatedev.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=restatedev) +# Restate Service Protocol -# Restate JVM SDK +This repo contains specification documents and Protobuf schemas of the Restate Service Protocol. -[Restate](https://restate.dev/) is a system for easily building resilient applications using _distributed durable async/await_. This repository contains the Restate SDK for writing services using JVM languages. +* [Service invocation protocol specification](./service-invocation-protocol.md) -This SDK features: +## Development -- Implement Restate services using either: - - Java - - Kotlin coroutines -- Reuse the existing gRPC/Protobuf ecosystem to define service interfaces -- Deploy Restate services as: - - Non-blocking HTTP servers - - On AWS Lambda +To format the spec document: -## Community - -* πŸ€—οΈ [Join our online community](https://discord.gg/skW3AZ6uGd) for help, sharing feedback and talking to the community. -* πŸ“– [Check out our documentation](https://docs.restate.dev) to get quickly started! -* πŸ“£ [Follow us on Twitter](https://twitter.com/restatedev) for staying up to date. -* πŸ™‹ [Create a GitHub issue](https://github.com/restatedev/sdk-java/issues) for requesting a new feature or reporting a problem. -* 🏠 [Visit our GitHub org](https://github.com/restatedev) for exploring other repositories. - -## Using the SDK - -### tl;dr Use project templates - -To get started, follow the [Java quickstart](https://docs.restate.dev/quickstart/?sdk=jvm). - -### Setup a project (Java) - -Scaffold a project using the build tool of your choice. For example, with Gradle (Kotlin script): - -``` -gradle init --type java-application -``` - -Add the dependency [sdk-api](sdk-api): - -``` -implementation("dev.restate:sdk-api:0.6.0") -``` - -Now you need to configure the protobuf plugin to build your Protobuf contracts. For example, with Gradle (Kotlin script): - -```kts -import com.google.protobuf.gradle.id - -plugins { - // ... - id("com.google.protobuf") version "0.9.1" - // ... -} - -dependencies { - // ... - // You need the following dependencies to compile the generated code - implementation("com.google.protobuf:protobuf-java:3.24.3") - implementation("io.grpc:grpc-stub:1.58.0") - implementation("io.grpc:grpc-protobuf:1.58.0") - compileOnly("org.apache.tomcat:annotations-api:6.0.53") -} - -// Configure protoc plugin -protobuf { - protoc { artifact = "com.google.protobuf:protoc:3.24.3" } - - plugins { - // The Restate plugin depends on the gRPC generated code - id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.58.0" } - id("restate") { artifact = "dev.restate:protoc-gen-restate:0.6.0:all@jar" } - } - - generateProtoTasks { - all().forEach { - it.plugins { - id("grpc") - id("restate") - } - } - } -} -``` - -For Maven you can use the [xolstice Protobuf plugin](https://www.xolstice.org/protobuf-maven-plugin/index.html). - -### Setup a project (Kotlin) - -Scaffold a project using the build tool of your choice. For example, with Gradle (Kotlin script): - -``` -gradle init --type kotlin-application -``` - -Add the dependency [`sdk-api-kotlin`](sdk-api-kotlin): - -``` -implementation("dev.restate:sdk-api-kotlin:0.6.0") -``` - -Now you need to configure the protobuf plugin to build your Protobuf contracts. For example, with Gradle (Kotlin script): - -```kts -import com.google.protobuf.gradle.id - -plugins { - // ... - id("com.google.protobuf") version "0.9.1" - // ... -} - -dependencies { - // ... - // You need the following dependencies to compile the generated code - implementation("com.google.protobuf:protobuf-java:3.24.3") - implementation("com.google.protobuf:protobuf-kotlin:3.24.3") - implementation("io.grpc:grpc-stub:1.58.0") - implementation("io.grpc:grpc-protobuf:1.58.0") - implementation("io.grpc:grpc-kotlin-stub:1.4.0") { exclude("javax.annotation", "javax.annotation-api") } - compileOnly("org.apache.tomcat:annotations-api:6.0.53") -} - -// Configure protoc plugin -protobuf { - protoc { artifact = "com.google.protobuf:protoc:3.24.3" } - - plugins { - // The gRPC Kotlin plugin depends on the gRPC generated code - id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.58.0" } - id("restate") { artifact = "dev.restate:protoc-gen-restate:0.6.0:all@jar" } - } - - generateProtoTasks { - all().forEach { - it.plugins { - id("grpc") - id("restate") { - option("kotlin") - } - } - it.builtins { - // The Kotlin codegen depends on the Java generated code - java {} - id("kotlin") - } - } - } -} -``` - -### Add the Protobuf contract - -Now you can add the Protobuf contract under `src/main/proto`. For example: - -```protobuf -syntax = "proto3"; -package greeter; - -option java_package = "my.greeter"; -option java_outer_classname = "GreeterProto"; - -import "dev/restate/ext.proto"; - -service Greeter { - option (dev.restate.ext.service_type) = KEYED; - - rpc Greet (GreetRequest) returns (GreetResponse); -} - -message GreetRequest { - string name = 1 [(dev.restate.ext.field) = KEY]; -} - -message GreetResponse { - string message = 1; -} -``` - -By using the Gradle or Maven plugin, the code is automatically re-generated on every build. - -### Implement the service (Java) - -Implement the service in a new class, for example: - -```java -public class Greeter extends GreeterRestate.GreeterRestateImplBase { - - private static final StateKey COUNT = StateKey.of("total", CoreSerdes.LONG); - - @Override - public GreetResponse greet(RestateContext ctx, GreetRequest request) { - long count = ctx.get(COUNT).orElse(0L); - ctx.set(COUNT, count + 1); - - return GreetResponse.newBuilder() - .setMessage(String.format("Hello %s for the %d time!", request.getName(), count)) - .build(); - } -} -``` - -If you want to use POJOs for state, check [how to use Jackson](#state-serde-using-jackson). - -### Implement the service (Kotlin) - -Implement the service in a new class, for example: - -```kotlin -class Greeter : GreeterRestateKtImplBase() { - companion object { - private val COUNT = StateKey.of("total", CoreSerdes.LONG) - } - - override suspend fun greet(context: RestateContext, request: GreetRequest): GreetResponse { - val count = context.get(COUNT) ?: 0L - context.set(COUNT, count + 1) - return greetResponse { message = "Hello ${request.name} for the $count time!" } - } -} -``` - -If you want to use POJOs for state, check [how to use Jackson](#state-serde-using-jackson). - -### Deploy the service (HTTP Server) - -To deploy the Restate service as HTTP server, add [`sdk-http-vertx`](sdk-http-vertx) to the dependencies. For example, in Gradle: - -``` -implementation("dev.restate:sdk-http-vertx:0.6.0") -``` - -To deploy the service, add the following code to the `main`. For example in Java: - -```java -public static void main(String[] args) { - RestateHttpEndpointBuilder.builder() - .withService(new Greeter()) - .buildAndListen(); -} -``` - -In Kotlin: - -```kotlin -fun main() { - RestateHttpEndpointBuilder.builder() - .withService(Greeter()) - .buildAndListen() -} -``` - -Execute the project. For example, using Gradle: - -``` -gradle run -``` - -### Deploy the service (AWS Lambda) - -To deploy the Restate service as Lambda, add [`sdk-lambda`](sdk-lambda) to the dependencies. For example, in Gradle: - -``` -implementation("dev.restate:sdk-lambda:0.6.0") -``` - -Configure the build tool to generate Fat-JARs, which are required by AWS Lambda to correctly load the JAR. For example, using Gradle: - -``` -plugins { - // ... - // The shadow plugin generates a shadow JAR ready for AWS Lambda - id("com.github.johnrengelman.shadow").version("7.1.2") - // ... -} -``` - -Now create the Lambda handler invoking the service. For example, in Java: - -```java -public class MyLambdaHandler extends BaseRestateLambdaHandler { - @Override - public void register(RestateLambdaEndpointBuilder builder) { - builder.withService(new Greeter()); - } -} -``` - -In Kotlin: - -```kotlin -class MyLambdaHandler : BaseRestateLambdaHandler { - override fun register(builder: RestateLambdaEndpointBuilder) { - builder.withService(Greeter()) - } -} -``` - -Now build the Fat-JAR. For example, using Gradle: - -``` -gradle shadowJar -``` - -You can now upload the generated Jar in AWS Lambda, and configure `MyLambdaHandler` as the Lambda class in the AWS UI. - -### Additional setup - -#### State ser/de using Jackson - -State ser/de is defined by the interface `Serde`. If you want to use [Jackson Databind](https://github.com/FasterXML/jackson) to ser/de POJOs to JSON, add the dependency [`sdk-serde-jackson`](sdk-serde-jackson). - -For example, in Gradle: - -``` -implementation("dev.restate:sdk-serde-jackson:0.6.0") -``` - -And then use `JacksonSerdes`: - -```java -private static final StateKey PERSON = StateKey.of("person", JacksonSerdes.of(Person.class)); -``` - -#### Logging - -The SDK uses log4j2 as logging facade. To enable logging, add the `log4j2` implementation to the dependencies: - -``` -implementation("org.apache.logging.log4j:log4j-core:2.20.0") -``` - -And configure the logging adding the file `resources/log4j2.properties`: - -``` -# Set to debug or trace if log4j initialization is failing -status = warn - -# Console appender configuration -appender.console.type = Console -appender.console.name = consoleLogger -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %notEmpty{[%X{restateServiceMethod}]}%notEmpty{[%X{restateInvocationId}]} %c - %m%n - -# Filter out logging during replay -appender.console.filter.replay.type = ContextMapFilter -appender.console.filter.replay.onMatch = DENY -appender.console.filter.replay.onMismatch = NEUTRAL -appender.console.filter.replay.0.type = KeyValuePair -appender.console.filter.replay.0.key = restateInvocationStatus -appender.console.filter.replay.0.value = REPLAYING - -# Restate logs to debug level -logger.app.name = dev.restate -logger.app.level = info -logger.app.additivity = false -logger.app.appenderRef.console.ref = consoleLogger - -# Root logger -rootLogger.level = info -rootLogger.appenderRef.stdout.ref = consoleLogger -``` - -The SDK injects the following additional metadata to the logging context that can be used for filtering as well: - -* `restateServiceMethod`: service and method, e.g. `counter.Counter/Add`. -* `restateInvocationId`: Invocation identifier, to be used in Restate observability tools. See https://docs.restate.dev/services/invocation#invocation-identifier. -* `restateInvocationStatus`: Invocation status, can be `WAITING_START`, `REPLAYING`, `PROCESSING`, `CLOSED`. - -#### Tracing with OpenTelemetry - -The SDK can generate additional tracing information on top of what Restate already publishes. See https://docs.restate.dev/restate/tracing to configure Restate tracing. - -You can the additional SDK tracing information by configuring the `OpenTelemetry` in the `RestateHttpEndpointBuilder`/`LambdaRestateServer`. - -For example, to set up tracing using environment variables, add the following modules to your dependencies: - -``` -implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.31.0") -implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.31.0") -``` - -And then configure it in the Restate builder: - -```java -.withOpenTelemetry(AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk()) -``` - -By exporting the following environment variables the OpenTelemetry SDK will be automatically configured to push traces: - -```shell -export OTEL_SERVICE_NAME=my-service -export OTEL_TRACES_SAMPLER=always_on -export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:14250 -``` - -Please refer to the [Opentelemetry manual instrumentation documentation](https://opentelemetry.io/docs/instrumentation/java/manual/#manual-instrumentation-setup) and the [autoconfigure documentation](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md) for more info. - -## Contributing - -We’re excited if you join the Restate community and start contributing! -Whether it is feature requests, bug reports, ideas & feedback or PRs, we appreciate any and all contributions. -We know that your time is precious and, therefore, deeply value any effort to contribute! - -### Building the SDK locally - -Prerequisites: - -- JDK >= 11 -- Docker or Podman - -To build the SDK: - -```shell -./gradlew build -``` - -To run the tests: - -```shell -./gradlew check -``` - -To publish local snapshots of the project: - -```shell -./gradlew -DskipSigning publishToMavenLocal -``` - -To update the [`proto`](https://github.com/restatedev/proto/) git subtree: - -```shell -git subtree pull --prefix sdk-common/src/main/proto/ git@github.com:restatedev/proto.git main --squash -``` - -To update the [`service-protocol`](https://github.com/restatedev/service-protocol/) git subtree: - -```shell -git subtree pull --prefix sdk-core/src/main/service-protocol/ git@github.com:restatedev/service-protocol.git main --squash ``` +npx prettier -w service-invocation-protocol.md +``` \ No newline at end of file diff --git a/admin-client/build.gradle.kts b/admin-client/build.gradle.kts deleted file mode 100644 index 1718e6aa..00000000 --- a/admin-client/build.gradle.kts +++ /dev/null @@ -1,56 +0,0 @@ -import net.ltgt.gradle.errorprone.errorprone -import org.openapitools.generator.gradle.plugin.tasks.GenerateTask - -plugins { - `java-library` - id("org.openapi.generator") version "6.6.0" - `library-publishing-conventions` -} - -description = "Code-generated Admin API client for Restate" - -dependencies { - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.core) - implementation(jacksonLibs.jackson.databind) - implementation(jacksonLibs.jackson.jsr310) - implementation("org.openapitools:jackson-databind-nullable:0.2.6") - - // Required for the annotations - compileOnly("org.apache.tomcat:annotations-api:6.0.53") - compileOnly("com.google.code.findbugs:jsr305:3.0.2") -} - -// Add generated output to source sets -sourceSets { main { java.srcDir(tasks.named("openApiGenerate")) } } - -// Configure openapi generator -tasks.withType { - inputSpec.set("$projectDir/src/main/openapi/meta.json") - - // Java 9+ HTTP Client using Jackson - generatorName.set("java") - library.set("native") - - // Package names - invokerPackage.set("dev.restate.admin.client") - apiPackage.set("dev.restate.admin.api") - modelPackage.set("dev.restate.admin.model") - - // We don't need these - generateApiTests.set(false) - generateApiDocumentation.set(false) - generateModelTests.set(false) - generateModelDocumentation.set(false) - - finalizedBy("spotlessJava") -} - -tasks.withType().configureEach { - // Disable errorprone for this module - options.errorprone.disableAllChecks.set(true) -} - -configure { - java { targetExclude(fileTree("build/generate-resources") { include("**/*.java") }) } -} diff --git a/admin-client/src/main/openapi/meta.json b/admin-client/src/main/openapi/meta.json deleted file mode 100644 index 1047fb9b..00000000 --- a/admin-client/src/main/openapi/meta.json +++ /dev/null @@ -1 +0,0 @@ -{"openapi":"3.0.0","info":{"title":"Admin API","version":"0.8.0"},"paths":{"/deployments":{"get":{"tags":["deployment"],"summary":"List deployments","description":"List all registered deployments.","operationId":"list_deployments","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDeploymentsResponse"}}}}}},"post":{"tags":["deployment"],"summary":"Create deployment","description":"Create deployment. Restate will invoke the endpoint to gather additional information required for registration, such as the components exposed by the deployment. If the deployment is already registered, this method will fail unless `force` is set to `true`.","operationId":"create_deployment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeploymentRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeploymentResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/subscriptions/{subscription}":{"get":{"tags":["subscription"],"summary":"Get subscription","description":"Get subscription","operationId":"get_subscription","parameters":[{"name":"subscription","in":"path","description":"Subscription identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}},"delete":{"tags":["subscription"],"summary":"Delete subscription","description":"Delete subscription.","operationId":"delete_subscription","parameters":[{"name":"subscription","in":"path","description":"Subscription identifier","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/components/{component}":{"get":{"tags":["component"],"summary":"Get component","description":"Get a registered component.","operationId":"get_component","parameters":[{"name":"component","in":"path","description":"Fully qualified component name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComponentMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}},"patch":{"tags":["component"],"summary":"Modify a component","description":"Modify a registered component.","operationId":"modify_component","parameters":[{"name":"component","in":"path","description":"Fully qualified component name.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifyComponentRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComponentMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/invocations/{invocation_id}":{"delete":{"tags":["invocation"],"summary":"Terminate an invocation","description":"Terminate the given invocation. By default, an invocation is terminated by gracefully cancelling it. This ensures service state consistency. Alternatively, an invocation can be killed which does not guarantee consistency for service instance state, in-flight invocation to other services, etc.","operationId":"terminate_invocation","parameters":[{"name":"invocation_id","in":"path","description":"Invocation identifier.","required":true,"schema":{"type":"string"}},{"name":"mode","in":"query","description":"If cancel, it will gracefully terminate the invocation. If kill, it will terminate the invocation with a hard stop.","style":"simple","schema":{"$ref":"#/components/schemas/TerminationMode"}}],"responses":{"202":{"description":"Accepted"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/components/{component}/handlers/{handler}":{"get":{"tags":["component_handler"],"summary":"Get component handler","description":"Get the handler of a component","operationId":"get_component_handler","parameters":[{"name":"component","in":"path","description":"Fully qualified component name.","required":true,"schema":{"type":"string"}},{"name":"handler","in":"path","description":"Handler name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandlerMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/openapi":{"get":{"tags":["openapi"],"summary":"OpenAPI specification","externalDocs":{"url":"https://swagger.io/specification/"},"operationId":"openapi_spec","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/components/{component}/handlers":{"get":{"tags":["component_handler"],"summary":"List component handlers","description":"List all the handlers of the given component.","operationId":"list_component_handlers","parameters":[{"name":"component","in":"path","description":"Fully qualified component name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListComponentHandlersResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/subscriptions":{"get":{"tags":["subscription"],"summary":"List subscriptions","description":"List all subscriptions.","operationId":"list_subscriptions","parameters":[{"name":"sink","in":"query","description":"Filter by the exact specified sink.","style":"simple","schema":{"type":"string"}},{"name":"source","in":"query","description":"Filter by the exact specified source.","style":"simple","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSubscriptionsResponse"}}}}}},"post":{"tags":["subscription"],"summary":"Create subscription","description":"Create subscription.","operationId":"create_subscription","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubscriptionRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/components":{"get":{"tags":["component"],"summary":"List components","description":"List all registered components.","operationId":"list_components","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListComponentsResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"Health check","description":"Check REST API Health.","operationId":"health","responses":{"200":{"description":"OK"}}}},"/components/{component}/state":{"post":{"tags":["component"],"summary":"Modify a component state","description":"Modify component state","operationId":"modify_component_state","parameters":[{"name":"component","in":"path","description":"Fully qualified component name.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifyComponentStateRequest"}}},"required":true},"responses":{"202":{"description":"Accepted"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/deployments/{deployment}":{"get":{"tags":["deployment"],"summary":"Get deployment","description":"Get deployment metadata","operationId":"get_deployment","parameters":[{"name":"deployment","in":"path","description":"Deployment identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedDeploymentResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}},"delete":{"tags":["deployment"],"summary":"Delete deployment","description":"Delete deployment. Currently it's supported to remove a deployment only using the force flag","operationId":"delete_deployment","parameters":[{"name":"deployment","in":"path","description":"Deployment identifier","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","description":"If true, the deployment will be forcefully deleted. This might break in-flight invocations, use with caution.","style":"simple","schema":{"type":"boolean"}}],"responses":{"202":{"description":"Accepted"},"501":{"description":"Not implemented. Only using the force flag is supported at the moment."},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}}},"components":{"schemas":{"RegisterDeploymentRequest":{"anyOf":[{"type":"object","required":["uri"],"properties":{"uri":{"title":"Uri","description":"Uri to use to discover/invoke the http deployment.","type":"string"},"additional_headers":{"title":"Additional headers","description":"Additional headers added to the discover/invoke requests to the deployment.","type":"object","additionalProperties":{"type":"string"},"nullable":true},"force":{"title":"Force","description":"If `true`, it will override, if existing, any deployment using the same `uri`. Beware that this can lead in-flight invocations to an unrecoverable error state.\n\nBy default, this is `true` but it might change in future to `false`.\n\nSee the [versioning documentation](https://docs.restate.dev/services/upgrades-removal) for more information.","default":true,"type":"boolean"},"dry_run":{"title":"Dry-run mode","description":"If `true`, discovery will run but the deployment will not be registered. This is useful to see the impact of a new deployment before registering it.","default":false,"type":"boolean"}}},{"type":"object","required":["arn"],"properties":{"arn":{"title":"ARN","description":"ARN to use to discover/invoke the lambda deployment.","type":"string"},"assume_role_arn":{"title":"Assume role ARN","description":"Optional ARN of a role to assume when invoking the addressed Lambda, to support role chaining","type":"string","nullable":true},"additional_headers":{"title":"Additional headers","description":"Additional headers added to the discover/invoke requests to the deployment.","type":"object","additionalProperties":{"type":"string"},"nullable":true},"force":{"title":"Force","description":"If `true`, it will override, if existing, any deployment using the same `uri`. Beware that this can lead in-flight invocations to an unrecoverable error state.\n\nBy default, this is `true` but it might change in future to `false`.\n\nSee the [versioning documentation](https://docs.restate.dev/services/upgrades-removal) for more information.","default":true,"type":"boolean"},"dry_run":{"title":"Dry-run mode","description":"If `true`, discovery will run but the deployment will not be registered. This is useful to see the impact of a new deployment before registering it.","default":false,"type":"boolean"}}}]},"RegisterDeploymentResponse":{"type":"object","required":["components","id"],"properties":{"id":{"$ref":"#/components/schemas/String"},"components":{"type":"array","items":{"$ref":"#/components/schemas/ComponentMetadata"}}}},"String":{"type":"string"},"ComponentMetadata":{"type":"object","required":["deployment_id","handlers","name","public","revision","ty"],"properties":{"name":{"title":"Name","description":"Fully qualified name of the component","type":"string"},"handlers":{"type":"array","items":{"$ref":"#/components/schemas/HandlerMetadata"}},"ty":{"$ref":"#/components/schemas/ComponentType"},"deployment_id":{"title":"Deployment Id","description":"Deployment exposing the latest revision of the component.","type":"string"},"revision":{"title":"Revision","description":"Latest revision of the component.","type":"integer","format":"uint32","minimum":0.0},"public":{"title":"Public","description":"If true, the component can be invoked through the ingress. If false, the component can be invoked only from another Restate service.","type":"boolean"}}},"HandlerMetadata":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"input_description":{"type":"string","nullable":true},"output_description":{"type":"string","nullable":true}}},"ComponentType":{"type":"string","enum":["Service","VirtualObject"]},"ErrorDescriptionResponse":{"title":"Error description response","description":"Error details of the response","type":"object","required":["message"],"properties":{"message":{"type":"string"},"restate_code":{"title":"Restate code","description":"Restate error code describing this error","type":"string","nullable":true}}},"SubscriptionResponse":{"type":"object","required":["id","options","sink","source"],"properties":{"id":{"$ref":"#/components/schemas/String"},"source":{"type":"string"},"sink":{"type":"string"},"options":{"type":"object","additionalProperties":{"type":"string"}}}},"TerminationMode":{"type":"string","enum":["Cancel","Kill"]},"ListDeploymentsResponse":{"type":"object","required":["deployments"],"properties":{"deployments":{"type":"array","items":{"$ref":"#/components/schemas/DeploymentResponse"}}}},"DeploymentResponse":{"type":"object","anyOf":[{"type":"object","required":["created_at","protocol_type","uri"],"properties":{"uri":{"type":"string"},"protocol_type":{"$ref":"#/components/schemas/ProtocolType"},"additional_headers":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"}}},{"type":"object","required":["arn","created_at"],"properties":{"arn":{"$ref":"#/components/schemas/LambdaARN"},"assume_role_arn":{"type":"string","nullable":true},"additional_headers":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"}}}],"required":["components","id"],"properties":{"id":{"$ref":"#/components/schemas/String"},"components":{"title":"Components","description":"List of components exposed by this deployment.","type":"array","items":{"$ref":"#/components/schemas/ComponentNameRevPair"}}}},"ComponentNameRevPair":{"type":"object","required":["name","revision"],"properties":{"name":{"type":"string"},"revision":{"type":"integer","format":"uint32","minimum":0.0}}},"ProtocolType":{"type":"string","enum":["RequestResponse","BidiStream"]},"LambdaARN":{"type":"string","format":"arn"},"ListComponentHandlersResponse":{"type":"object","required":["handlers"],"properties":{"handlers":{"type":"array","items":{"$ref":"#/components/schemas/HandlerMetadata"}}}},"CreateSubscriptionRequest":{"type":"object","required":["sink","source"],"properties":{"source":{"title":"Identifier","description":"# Source\n\nSource uri. Accepted forms:\n\n* `kafka:///`, e.g. `service://my-cluster/my-topic`","type":"string"},"sink":{"title":"Sink","description":"Sink uri. Accepted forms:\n\n* `service:///`, e.g. `service://com.example.MySvc/MyMethod`","type":"string"},"options":{"title":"Options","description":"Additional options to apply to the subscription.","type":"object","additionalProperties":{"type":"string"},"nullable":true}}},"ListComponentsResponse":{"type":"object","required":["components"],"properties":{"components":{"type":"array","items":{"$ref":"#/components/schemas/ComponentMetadata"}}}},"ListSubscriptionsResponse":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"ModifyComponentStateRequest":{"type":"object","required":["new_state","object_key"],"properties":{"version":{"title":"Version","description":"If set, the latest version of the state is compared with this value and the operation will fail when the versions differ.","type":"string","nullable":true},"object_key":{"title":"Component key","description":"To what virtual object key to apply this change","type":"string"},"new_state":{"title":"New State","description":"The new state to replace the previous state with","type":"object","additionalProperties":{"type":"array","items":{"type":"integer","format":"uint8","minimum":0.0}}}}},"ModifyComponentRequest":{"type":"object","required":["public"],"properties":{"public":{"title":"Public","description":"If true, the component can be invoked through the ingress. If false, the component can be invoked only from another Restate service.","type":"boolean"}}},"DetailedDeploymentResponse":{"type":"object","anyOf":[{"type":"object","required":["created_at","protocol_type","uri"],"properties":{"uri":{"type":"string"},"protocol_type":{"$ref":"#/components/schemas/ProtocolType"},"additional_headers":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"}}},{"type":"object","required":["arn","created_at"],"properties":{"arn":{"$ref":"#/components/schemas/LambdaARN"},"assume_role_arn":{"type":"string","nullable":true},"additional_headers":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"}}}],"required":["components","id"],"properties":{"id":{"$ref":"#/components/schemas/String"},"components":{"title":"Components","description":"List of components exposed by this deployment.","type":"array","items":{"$ref":"#/components/schemas/ComponentMetadata"}}}}}}} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 6b8ab5f5..00000000 --- a/build.gradle.kts +++ /dev/null @@ -1,124 +0,0 @@ -import net.ltgt.gradle.errorprone.errorprone - -plugins { - java - kotlin("jvm") version "1.9.22" apply false - kotlin("plugin.serialization") version "1.9.22" apply false - - id("net.ltgt.errorprone") version "3.0.1" - id("com.github.jk1.dependency-license-report") version "2.0" - id("io.github.gradle-nexus.publish-plugin") version "1.3.0" - - alias(pluginLibs.plugins.spotless) -} - -val protobufVersion = coreLibs.versions.protobuf.get() -val restateVersion = libs.versions.restate.get() - -allprojects { - apply(plugin = "com.diffplug.spotless") - apply(plugin = "com.github.jk1.dependency-license-report") - - group = "dev.restate" - version = restateVersion - - configure { - java { - targetExclude("build/generated/**/*.java") - - googleJavaFormat() - - licenseHeaderFile("$rootDir/config/license-header") - } - - format("proto") { - target("**/*.proto") - - // Exclude proto and service-protocol directories because those get the license header from - // their repos. - targetExclude( - fileTree("$rootDir/sdk-common/src/main/proto") { include("**/*.*") }, - fileTree("$rootDir/sdk-core/src/main/service-protocol") { include("**/*.*") }) - - licenseHeaderFile("$rootDir/config/license-header", "syntax") - } - - kotlin { - targetExclude("build/generated/**/*.kt") - ktfmt() - licenseHeaderFile("$rootDir/config/license-header") - } - - kotlinGradle { ktfmt() } - - format("properties") { - target("**/*.properties") - trimTrailingWhitespace() - } - } - - tasks { check { dependsOn(checkLicense) } } - - licenseReport { - renderers = arrayOf(com.github.jk1.license.render.CsvReportRenderer()) - - excludeBoms = true - - excludes = - arrayOf( - "io.vertx:vertx-stack-depchain", // Vertx bom file - "com.google.guava:guava-parent", // Guava bom - // kotlinx dependencies are APL 2, but somehow the plugin doesn't recognize that. - "org.jetbrains.kotlinx:kotlinx-coroutines-core", - "org.jetbrains.kotlinx:kotlinx-serialization-core", - "org.jetbrains.kotlinx:kotlinx-serialization-json", - ) - - allowedLicensesFile = file("$rootDir/config/allowed-licenses.json") - filters = - arrayOf( - com.github.jk1.license.filter.LicenseBundleNormalizer( - "$rootDir/config/license-normalizer-bundle.json", true)) - } -} - -subprojects { - apply(plugin = "java") - apply(plugin = "net.ltgt.errorprone") - - dependencies { errorprone("com.google.errorprone:error_prone_core:2.13.1") } - - // Configure the java toolchain to use. If not found, it will be downloaded automatically - java { - toolchain { languageVersion = JavaLanguageVersion.of(11) } - - withJavadocJar() - withSourcesJar() - } - - tasks.withType().configureEach { - options.errorprone.disableWarningsInGeneratedCode.set(true) - options.errorprone.disable( - // We use toString() in proto messages for debugging reasons. - "LiteProtoToString", - // This check is proposing to use a guava API instead... - "StringSplitter", - // This is conflicting with a javadoc warn lint - "MissingSummary") - options.errorprone.excludedPaths.set(".*/build/generated/.*") - } - - tasks.withType { useJUnitPlatform() } -} - -nexusPublishing { - repositories { - sonatype { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - - username.set(System.getenv("MAVEN_CENTRAL_USERNAME") ?: return@sonatype) - password.set(System.getenv("MAVEN_CENTRAL_TOKEN") ?: return@sonatype) - } - } -} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 876c922b..00000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -plugins { - `kotlin-dsl` -} - -repositories { - mavenCentral() -} diff --git a/buildSrc/src/main/kotlin/library-publishing-conventions.gradle.kts b/buildSrc/src/main/kotlin/library-publishing-conventions.gradle.kts deleted file mode 100644 index e5701047..00000000 --- a/buildSrc/src/main/kotlin/library-publishing-conventions.gradle.kts +++ /dev/null @@ -1,56 +0,0 @@ -plugins { - `maven-publish` - signing -} - -project.afterEvaluate { - publishing { - publications { - create("maven") { - groupId = "dev.restate" - artifactId = project.name - - from(components["java"]) - - pom { - name = "Restate SDK :: ${project.name}" - description = project.description!! - url = "https://github.com/restatedev/sdk-java" - inceptionYear = "2023" - - licenses { - license { - name = "MIT License" - url = "https://opensource.org/license/mit/" - } - } - - scm { - connection = "scm:git:git://github.com/restatedev/sdk-java.git" - developerConnection = "scm:git:ssh://github.com/restatedev/sdk-java.git" - url = "https://github.com/restatedev/sdk-java" - } - - developers { - developer { - name = "Francesco Guardiani" - id = "slinkydeveloper" - email = "francescoguard@gmail.com" - } - } - } - } - } - } - - signing { - setRequired { !project.hasProperty("skipSigning") } - - val key = System.getenv("MAVEN_CENTRAL_GPG_PRIVATE_KEY") ?: return@signing - val password = System.getenv("MAVEN_CENTRAL_GPG_PASSPHRASE") ?: return@signing - val publishing: PublishingExtension by project - - useInMemoryPgpKeys(key, password) - sign(publishing.publications["maven"]) - } -} \ No newline at end of file diff --git a/config/allowed-licenses.json b/config/allowed-licenses.json deleted file mode 100644 index 7ef70493..00000000 --- a/config/allowed-licenses.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "allowedLicenses": [ - { - "moduleLicense": "Apache License, Version 2.0" - }, - { - "moduleLicense": "The MIT License (MIT)" - }, - { - "moduleLicense": "The 3-Clause BSD License" - }, - { - "moduleLicense": "The 2-Clause BSD License" - }, - { - "moduleLicense": "Eclipse Public License - v 2.0" - }, - { - "moduleLicense": "Eclipse Public License - v 1.0" - }, - { - "moduleName": "org.jetbrains.kotlin:kotlin-stdlib-common" - } - ] -} \ No newline at end of file diff --git a/config/license-header b/config/license-header deleted file mode 100644 index 0e5fe0c8..00000000 --- a/config/license-header +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE \ No newline at end of file diff --git a/config/license-normalizer-bundle.json b/config/license-normalizer-bundle.json deleted file mode 100644 index cc8ee5f4..00000000 --- a/config/license-normalizer-bundle.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "bundles": [ - { - "bundleName": "Apache-2.0", - "licenseName": "Apache License, Version 2.0", - "licenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" - }, - { - "bundleName": "MIT", - "licenseName": "The MIT License (MIT)" - } - ], - "transformationRules": [ - { - "bundleName": "Apache-2.0", - "licenseNamePattern": ".*The Apache Software License, Version 2\\.0.*" - }, - { - "bundleName": "Apache-2.0", - "licenseNamePattern": "Apache[ |-|_]2.*" - }, - { - "bundleName": "Apache-2.0", - "licenseNamePattern": "ASL 2\\.0" - }, - { - "bundleName": "Apache-2.0", - "licenseNamePattern": ".*Apache License,?( Version)? 2.*" - }, - { - "bundleName": "MIT", - "licenseNamePattern": ".*MIT License.*" - } - ] -} \ No newline at end of file diff --git a/deployment_manifest_schema.json b/deployment_manifest_schema.json new file mode 100644 index 00000000..6695b595 --- /dev/null +++ b/deployment_manifest_schema.json @@ -0,0 +1,135 @@ +{ + "$id": "https://restate.dev/deployment.manifest.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Deployment", + "description": "Restate deployment manifest", + "properties": { + "protocolMode": { + "title": "ProtocolMode", + "enum": ["BIDI_STREAM", "REQUEST_RESPONSE"] + }, + "minProtocolVersion": { + "type": "integer", + "minimum": 0 + }, + "maxProtocolVersion": { + "type": "integer", + "maximum": 0 + }, + "components": { + "type": "array", + "items": { + "type": "object", + "title": "Component", + "properties": { + "fullyQualifiedComponentName": { + "type": "string", + "pattern": "^([a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z0-9._-]*$" + }, + "componentType": { + "title": "ComponentType", + "enum": ["VIRTUAL_OBJECT", "SERVICE"] + }, + "handlers": { + "type": "array", + "items": { + "type": "object", + "title": "Handler", + "properties": { + "name": { + "type": "string", + "pattern": "^([a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z0-9_]*$" + }, + "handlerType": { + "title": "HandlerType", + "enum": ["EXCLUSIVE", "SHARED"], + "description": "If unspecified, defaults to EXCLUSIVE for Virtual Object. This should be unset for Services." + }, + "input": { + "type": "object", + "title": "InputPayload", + "description": "Description of an input payload. This will be used by Restate to validate incoming requests.", + "properties": { + "required": { + "type": "boolean", + "description": "If true, a body MUST be sent with a content-type, even if the body length is zero." + }, + "contentType": { + "type": "string", + "description": "Content type of the input. It can accept wildcards, in the same format as the 'Accept' header. When this field is unset, it implies emptiness, meaning no content-type/body is expected." + }, + "jsonSchema": {} + }, + "additionalProperties": false, + "default": { + "contentType": "*/*", + "required": false + }, + "examples": { + "empty input": {}, + "non empty json input": { + "required": true, + "contentType": "application/json", + "jsonSchema": true + }, + "either empty or non empty json input": { + "required": false, + "contentType": "application/json", + "jsonSchema": true + }, + "bytes input": { + "required": true, + "contentType": "application/octet-stream" + } + } + }, + "output": { + "type": "object", + "title": "OutputPayload", + "description": "Description of an output payload.", + "properties": { + "contentType": { + "type": "string", + "description": "Content type set on output. This will be used by Restate to set the output content type at the ingress." + }, + "setContentTypeIfEmpty": { + "type": "boolean", + "description": "If true, the specified content-type is set even if the output is empty." + }, + "jsonSchema": {} + }, + "additionalProperties": false, + "default": { + "contentType": "application/json", + "setContentTypeIfEmpty": false + }, + "examples": { + "empty output": { + "setContentTypeIfEmpty": false + }, + "non-empty json output": { + "contentType": "application/json", + "setContentTypeIfEmpty": false, + "jsonSchema": true + }, + "protobuf output": { + "contentType": "application/proto", + "setContentTypeIfEmpty": true + } + } + } + }, + "required": ["name"], + "additionalProperties": false + } + } + }, + "required": ["fullyQualifiedComponentName", "componentType", "handlers"], + "additionalProperties": false + } + } + }, + "required": ["minProtocolVersion", "maxProtocolVersion", "components"], + "additionalProperties": false +} diff --git a/sdk-core/src/main/service-protocol/dev/restate/service/protocol.proto b/dev/restate/service/protocol.proto similarity index 79% rename from sdk-core/src/main/service-protocol/dev/restate/service/protocol.proto rename to dev/restate/service/protocol.proto index 9c4fbce6..02fe4280 100644 --- a/sdk-core/src/main/service-protocol/dev/restate/service/protocol.proto +++ b/dev/restate/service/protocol.proto @@ -11,8 +11,6 @@ syntax = "proto3"; package dev.restate.service.protocol; -import "google/protobuf/empty.proto"; - option java_package = "dev.restate.generated.service.protocol"; option go_package = "restate.dev/sdk-go/pb/service/protocol"; @@ -49,7 +47,7 @@ message CompletionMessage { uint32 entry_index = 1; oneof result { - google.protobuf.Empty empty = 13; + Empty empty = 13; bytes value = 14; Failure failure = 15; }; @@ -68,17 +66,23 @@ message SuspensionMessage { // Type: 0x0000 + 3 message ErrorMessage { - // The code can be: - // * Any of the error codes defined by OutputStreamEntry.failure (see Failure message) - // * JOURNAL_MISMATCH = 32, that is when the SDK cannot replay a journal due to the mismatch between the journal and the actual code. - // * PROTOCOL_VIOLATION = 33, that is when the SDK receives an unexpected message or an expected message variant, given its state. - // - // If 16 < code < 32, or code > 33, the runtime will interpret it as code 2 (UNKNOWN). + // The code can be any HTTP status code, as described https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. + // In addition, we define the following error codes that MAY be used by the SDK for better error reporting: + // * JOURNAL_MISMATCH = 570, that is when the SDK cannot replay a journal due to the mismatch between the journal and the actual code. + // * PROTOCOL_VIOLATION = 571, that is when the SDK receives an unexpected message or an expected message variant, given its state. uint32 code = 1; // Contains a concise error message, e.g. Throwable#getMessage() in Java. string message = 2; // Contains a verbose error description, e.g. the exception stacktrace. string description = 3; + + // Entry that caused the failure. This may be outside the current stored journal size. + // If no specific entry caused the failure, the current replayed/processed entry can be used. + uint32 related_entry_index = 4; + // Name of the entry that caused the failure. Empty if no name was set. + string related_entry_name = 5; + // Entry type. 0 if unknown. + uint32 related_entry_type = 6; } // Type: 0x0000 + 4 @@ -100,7 +104,7 @@ message EndMessage { // * bytes value = 14 for carrying the result value // * Failure failure = 15 for carrying a failure // -// The tag numbers 13, 14 and 15 are reserved and shouldn't be used for other fields. +// The tag numbers 12, 13, 14 and 15 are reserved and shouldn't be used for other fields. // ------ Input and output ------ @@ -111,6 +115,9 @@ message InputEntryMessage { repeated Header headers = 1; bytes value = 14; + + // Entry name + string name = 12; } // Completable: No @@ -121,6 +128,9 @@ message OutputEntryMessage { bytes value = 14; Failure failure = 15; }; + + // Entry name + string name = 12; } // ------ State access ------ @@ -132,10 +142,13 @@ message GetStateEntryMessage { bytes key = 1; oneof result { - google.protobuf.Empty empty = 13; + Empty empty = 13; bytes value = 14; Failure failure = 15; }; + + // Entry name + string name = 12; } // Completable: No @@ -144,6 +157,9 @@ message GetStateEntryMessage { message SetStateEntryMessage { bytes key = 1; bytes value = 3; + + // Entry name + string name = 12; } // Completable: No @@ -151,12 +167,17 @@ message SetStateEntryMessage { // Type: 0x0800 + 2 message ClearStateEntryMessage { bytes key = 1; + + // Entry name + string name = 12; } // Completable: No // Fallible: No // Type: 0x0800 + 3 message ClearAllStateEntryMessage { + // Entry name + string name = 12; } // Completable: Yes @@ -171,6 +192,9 @@ message GetStateKeysEntryMessage { StateKeys value = 14; Failure failure = 15; }; + + // Entry name + string name = 12; } // ------ Syscalls ------ @@ -184,9 +208,12 @@ message SleepEntryMessage { uint64 wake_up_time = 1; oneof result { - google.protobuf.Empty empty = 13; + Empty empty = 13; Failure failure = 15; } + + // Entry name + string name = 12; } // Completable: Yes @@ -207,6 +234,9 @@ message InvokeEntryMessage { bytes value = 14; Failure failure = 15; }; + + // Entry name + string name = 12; } // Completable: No @@ -228,6 +258,9 @@ message BackgroundInvokeEntryMessage { // If this invocation has a key associated (e.g. for objects and workflows), then this key is filled in. Empty otherwise. string key = 6; + + // Entry name + string name = 12; } // Completable: Yes @@ -239,6 +272,9 @@ message AwakeableEntryMessage { bytes value = 14; Failure failure = 15; }; + + // Entry name + string name = 12; } // Completable: No @@ -252,6 +288,9 @@ message CompleteAwakeableEntryMessage { bytes value = 5; Failure failure = 6; }; + + // Entry name + string name = 12; } // --- Nested messages @@ -259,10 +298,7 @@ message CompleteAwakeableEntryMessage { // This failure object carries user visible errors, // e.g. invocation failure return value or failure result of an InvokeEntryMessage. message Failure { - // The code should be any of the gRPC error codes, - // as defined here: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md#status-codes-and-their-use-in-grpc - // - // If code > 16, the runtime will interpret it as code 2 (UNKNOWN). + // The code can be any HTTP status code, as described https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. uint32 code = 1; // Contains a concise error message, e.g. Throwable#getMessage() in Java. string message = 2; @@ -272,3 +308,6 @@ message Header { string key = 1; string value = 2; } + +message Empty { +} \ No newline at end of file diff --git a/development/README.md b/development/README.md deleted file mode 100644 index 9e51af7d..00000000 --- a/development/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Developer documentation - -This directory contains documentation relevant for Restate developers. - -## Table of contents - -[developer-guidelines.md](developer-guidelines.md) contains guidelines for the development in this repository. \ No newline at end of file diff --git a/development/developer-guidelines.md b/development/developer-guidelines.md deleted file mode 100644 index d7ff47c3..00000000 --- a/development/developer-guidelines.md +++ /dev/null @@ -1,5 +0,0 @@ -# Developer guidelines - -The same guidelines as the ones -outlined [here](https://github.com/restatedev/runtime/blob/main/doc/dev/development-guidelines.md) apply to the -development in this repository. \ No newline at end of file diff --git a/development/release.md b/development/release.md deleted file mode 100644 index 5dd4166a..00000000 --- a/development/release.md +++ /dev/null @@ -1,11 +0,0 @@ -# Releasing sdk-java - -To release sdk-java: - -* First make sure `service-protocol` and `proto` subtrees are updated to the latest version. See the main [README.md](../README.md#contributing-to-the-sdk) -* Change the version to the desired version using the [Bump version workflow](https://github.com/restatedev/sdk-java/actions/workflows/bump.yaml). -* Merge the auto generated PR -* Wait for CI on main to execute the release -* Create the Github Release manually -* Change the version again to the next `-SNAPSHOT` version (e.g. from `0.4.0` to `0.5.0-SNAPSHOT`, from `0.4.1` to `0.5.0-SNAPSHOT`) -* Merge the auto generated PR diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 6ec7c2c2..00000000 --- a/examples/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Examples - -This directory contains different examples of the SDK features. - -For a sample project configuration and more elaborated examples, check out the [examples repo](https://github.com/restatedev/examples). - -Available examples: - -* [`Counter`](src/main/java/my/restate/sdk/examples/Counter.java): Shows a simple virtual object using state primitives. -* [`CounterKt`](src/main/kotlin/my/restate/sdk/examples/CounterKt.kt): Same as `Counter` but using Kotlin. -* [`LoanWorkflow`](src/main/java/my/restate/sdk/examples/LoanWorkflow.java): Shows a simple workflow example using the Workflow API. - -## Package the examples for Lambda - -Run: - -```shell -./gradlew shadowJar -``` - -You'll find the shadowed jar in the `build` directory. - -The class to configure in Lambda is `my.restate.sdk.examples.LambdaHandler`. - -By default, the [`my.restate.sdk.examples.Counter`](src/main/java/my/restate/sdk/examples/Counter.java) virtual object is deployed. Set the env variable `LAMBDA_FACTORY_SERVICE_CLASS` to one of the available example classes to change the deployed class. - -## Running the examples (HTTP) - -You can run the Java Counter example via: - -```shell -./gradlew :examples:run -``` - -You can modify the class to run setting `-PmainClass=`, for example, in order to run the Kotlin implementation: - -```shell -./gradlew :examples:run -PmainClass=my.restate.sdk.examples.CounterKtKt -``` - -## Invoking the counter bindableComponent - -If you want to invoke the counter virtual object via curl: - -```shell -# To add a new value to my-counter -curl http://localhost:8080/Counter/my-counter/add --json "1" -# To get my-counter value -curl http://localhost:8080/Counter/my-counter/get -``` - -The command assumes that the Restate runtime is reachable under `localhost:8080`. diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts deleted file mode 100644 index c75d67d5..00000000 --- a/examples/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer - -plugins { - java - kotlin("jvm") - kotlin("plugin.serialization") - application - alias(kotlinLibs.plugins.ksp) - id("com.github.johnrengelman.shadow").version("8.1.1") -} - -dependencies { - ksp(project(":sdk-api-kotlin-gen")) - annotationProcessor(project(":sdk-api-gen")) - - implementation(project(":sdk-api")) - implementation(project(":sdk-lambda")) - implementation(project(":sdk-http-vertx")) - implementation(project(":sdk-api-kotlin")) - implementation(project(":sdk-serde-jackson")) - implementation(project(":sdk-workflow-api")) - - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.jsr310) - - implementation(kotlinLibs.kotlinx.coroutines) - implementation(kotlinLibs.kotlinx.serialization.core) - implementation(kotlinLibs.kotlinx.serialization.json) - - implementation(coreLibs.log4j.core) -} - -application { - val mainClassValue: String = - project.findProperty("mainClass")?.toString() ?: "my.restate.sdk.examples.Counter" - mainClass.set(mainClassValue) -} - -tasks.withType { this.enabled = false } - -tasks.withType { transform(ServiceFileTransformer::class.java) } diff --git a/examples/src/main/java/my/restate/sdk/examples/Counter.java b/examples/src/main/java/my/restate/sdk/examples/Counter.java deleted file mode 100644 index 014dc2d3..00000000 --- a/examples/src/main/java/my/restate/sdk/examples/Counter.java +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package my.restate.sdk.examples; - -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@VirtualObject(name = "Counter") -public class Counter { - - private static final Logger LOG = LogManager.getLogger(Counter.class); - - private static final StateKey TOTAL = StateKey.of("total", CoreSerdes.JSON_LONG); - - @Handler - public void reset(ObjectContext ctx) { - ctx.clearAll(); - } - - @Handler - public void add(ObjectContext ctx, Long request) { - long currentValue = ctx.get(TOTAL).orElse(0L); - long newValue = currentValue + request; - ctx.set(TOTAL, newValue); - } - - @Handler - public Long get(ObjectContext ctx) { - return ctx.get(TOTAL).orElse(0L); - } - - @Handler - public CounterUpdateResult getAndAdd(ObjectContext ctx, Long request) { - LOG.info("Invoked get and add with " + request); - - long currentValue = ctx.get(TOTAL).orElse(0L); - long newValue = currentValue + request; - ctx.set(TOTAL, newValue); - - return new CounterUpdateResult(newValue, currentValue); - } - - public static void main(String[] args) { - RestateHttpEndpointBuilder.builder().with(new Counter()).buildAndListen(); - } - - public static class CounterUpdateResult { - private final Long newValue; - private final Long oldValue; - - public CounterUpdateResult(Long newValue, Long oldValue) { - this.newValue = newValue; - this.oldValue = oldValue; - } - - public Long getNewValue() { - return newValue; - } - - public Long getOldValue() { - return oldValue; - } - } -} diff --git a/examples/src/main/java/my/restate/sdk/examples/LambdaHandler.java b/examples/src/main/java/my/restate/sdk/examples/LambdaHandler.java deleted file mode 100644 index 264bddb1..00000000 --- a/examples/src/main/java/my/restate/sdk/examples/LambdaHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package my.restate.sdk.examples; - -import dev.restate.sdk.lambda.BaseRestateLambdaHandler; -import dev.restate.sdk.lambda.RestateLambdaEndpointBuilder; -import java.util.Objects; -import java.util.regex.Pattern; - -public class LambdaHandler extends BaseRestateLambdaHandler { - - @Override - public void register(RestateLambdaEndpointBuilder builder) { - for (String serviceClass : - Objects.requireNonNullElse( - System.getenv("LAMBDA_FACTORY_SERVICE_CLASS"), Counter.class.getCanonicalName()) - .split(Pattern.quote(","))) { - if (Counter.class.getCanonicalName().equals(serviceClass)) { - builder.with(new Counter()); - } else if (CounterKt.class.getCanonicalName().equals(serviceClass)) { - builder.with(new CounterKt()); - } else { - throw new IllegalArgumentException( - "Bad \"LAMBDA_FACTORY_SERVICE_CLASS\" env: " + serviceClass); - } - } - } -} diff --git a/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java b/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java deleted file mode 100644 index 1bbe11f6..00000000 --- a/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package my.restate.sdk.examples; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import dev.restate.sdk.Context; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; -import dev.restate.sdk.annotation.Shared; -import dev.restate.sdk.annotation.Workflow; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; -import dev.restate.sdk.serde.jackson.JacksonSerdes; -import dev.restate.sdk.workflow.DurablePromiseKey; -import dev.restate.sdk.workflow.WorkflowContext; -import dev.restate.sdk.workflow.WorkflowExecutionState; -import dev.restate.sdk.workflow.WorkflowSharedContext; -import java.math.BigDecimal; -import java.time.Duration; -import java.time.Instant; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@Workflow -public class LoanWorkflow { - - // --- Data types used by the Loan Worfklow - - enum Status { - SUBMITTED, - WAITING_HUMAN_APPROVAL, - APPROVED, - NOT_APPROVED, - TRANSFER_SUCCEEDED, - TRANSFER_FAILED - } - - public static class LoanRequest { - - private final String customerName; - private final String customerId; - private final String customerBankAccount; - private final BigDecimal amount; - - @JsonCreator - public LoanRequest( - @JsonProperty("customerName") String customerName, - @JsonProperty("customerId") String customerId, - @JsonProperty("customerBankAccount") String customerBankAccount, - @JsonProperty("amount") BigDecimal amount) { - this.customerName = customerName; - this.customerId = customerId; - this.customerBankAccount = customerBankAccount; - this.amount = amount; - } - - public String getCustomerName() { - return customerName; - } - - public String getCustomerId() { - return customerId; - } - - public String getCustomerBankAccount() { - return customerBankAccount; - } - - public BigDecimal getAmount() { - return amount; - } - } - - private static final Logger LOG = LogManager.getLogger(LoanWorkflow.class); - - private static final StateKey STATUS = - StateKey.of("status", JacksonSerdes.of(Status.class)); - private static final StateKey LOAN_REQUEST = - StateKey.of("loanRequest", JacksonSerdes.of(LoanRequest.class)); - private static final DurablePromiseKey HUMAN_APPROVAL = - DurablePromiseKey.of("humanApproval", CoreSerdes.JSON_BOOLEAN); - private static final StateKey TRANSFER_EXECUTION_TIME = - StateKey.of("transferExecutionTime", CoreSerdes.JSON_STRING); - - // --- The main workflow method - - @Workflow - public void run(WorkflowContext ctx, LoanRequest loanRequest) { - // 1. Set status - ctx.set(STATUS, Status.SUBMITTED); - ctx.set(LOAN_REQUEST, loanRequest); - - LOG.info("Loan request submitted"); - - // 2. Ask human approval - ctx.sideEffect(() -> askHumanApproval(ctx.workflowKey())); - ctx.set(STATUS, Status.WAITING_HUMAN_APPROVAL); - - // 3. Wait human approval - boolean approved = ctx.durablePromise(HUMAN_APPROVAL).awaitable().await(); - if (!approved) { - LOG.info("Not approved"); - ctx.set(STATUS, Status.NOT_APPROVED); - return; - } - LOG.info("Approved"); - ctx.set(STATUS, Status.APPROVED); - - // 4. Request money transaction to the bank - var bankClient = LoanWorkflowMockBankClient.fromContext(ctx); - Instant executionTime; - try { - executionTime = - bankClient - .transfer( - new TransferRequest( - loanRequest.getCustomerBankAccount(), loanRequest.getAmount())) - .await(Duration.ofDays(7)); - } catch (TerminalException | TimeoutException e) { - LOG.warn("Transaction failed", e); - ctx.set(STATUS, Status.TRANSFER_FAILED); - return; - } - - LOG.info("Transfer complete"); - - // 5. Transfer complete! - ctx.set(TRANSFER_EXECUTION_TIME, executionTime.toString()); - ctx.set(STATUS, Status.TRANSFER_SUCCEEDED); - } - - // --- Methods to approve/reject loan - - @Shared - public void approveLoan(WorkflowSharedContext ctx) { - ctx.durablePromiseHandle(HUMAN_APPROVAL).resolve(true); - } - - @Shared - public void rejectLoan(WorkflowSharedContext ctx) { - ctx.durablePromiseHandle(HUMAN_APPROVAL).resolve(false); - } - - public static void main(String[] args) { - RestateHttpEndpointBuilder.builder() - .with(new LoanWorkflow()) - .with(new MockBank()) - .buildAndListen(); - - // Register the service in the meantime! - LOG.info("Now it's time to register this deployment"); - - try { - Thread.sleep(20_000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - // To invoke the workflow: - LoanWorkflowClient.IngressClient client = - LoanWorkflowClient.fromIngress("http://127.0.0.1:8080", "my-loan"); - - WorkflowExecutionState state = - client.submit( - new LoanRequest( - "Francesco", "slinkydeveloper", "DE1234", new BigDecimal("1000000000"))); - if (state != WorkflowExecutionState.STARTED) { - throw new IllegalStateException("Unexpected state " + state); - } - - LOG.info("Started loan workflow"); - - // Takes some bureaucratic time to approve the loan - try { - Thread.sleep(10_000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - LOG.info("We took the decision to approve your loan! You can now achieve your dreams!"); - - // Now approve it - client.approveLoan(); - - while (!client.isCompleted()) { - LOG.info("Not completed yet"); - try { - Thread.sleep(10_000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - LOG.info("Loan workflow completed"); - } - - // -- Some mocks - - private static void askHumanApproval(String workflowKey) throws InterruptedException { - LOG.info("Sending human approval request"); - Thread.sleep(1000); - } - - @Service - static class MockBank { - @Handler - public Instant transfer(Context context, TransferRequest request) throws TerminalException { - boolean shouldAccept = context.random().nextInt(3) != 1; - if (shouldAccept) { - return Instant.now(); - } else { - throw new TerminalException("Won't accept the transfer"); - } - } - } - - public static class TransferRequest { - private final String bankAccount; - private final BigDecimal amount; - - @JsonCreator - public TransferRequest( - @JsonProperty("bankAccount") String bankAccount, - @JsonProperty("amount") BigDecimal amount) { - this.bankAccount = bankAccount; - this.amount = amount; - } - - public String getBankAccount() { - return bankAccount; - } - - public BigDecimal getAmount() { - return amount; - } - } -} diff --git a/examples/src/main/kotlin/my/restate/sdk/examples/CounterKt.kt b/examples/src/main/kotlin/my/restate/sdk/examples/CounterKt.kt deleted file mode 100644 index c17c3e68..00000000 --- a/examples/src/main/kotlin/my/restate/sdk/examples/CounterKt.kt +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package my.restate.sdk.examples - -import dev.restate.sdk.annotation.Handler -import dev.restate.sdk.annotation.VirtualObject -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder -import dev.restate.sdk.kotlin.KtSerdes -import dev.restate.sdk.kotlin.ObjectContext -import kotlinx.serialization.Serializable - -@Serializable data class CounterUpdate(var oldValue: Long, val newValue: Long) - -@VirtualObject -class CounterKt { - - companion object { - private val TOTAL = StateKey.of("total", KtSerdes.json()) - } - - @Handler - suspend fun reset(ctx: ObjectContext) { - ctx.clear(TOTAL) - } - - @Handler - suspend fun add(ctx: ObjectContext, value: Long) { - val currentValue = ctx.get(TOTAL) ?: 0L - val newValue = currentValue + value - ctx.set(TOTAL, newValue) - } - - @Handler - suspend fun get(ctx: ObjectContext): Long { - return ctx.get(TOTAL) ?: 0L - } - - @Handler - suspend fun getAndAdd(ctx: ObjectContext, value: Long): CounterUpdate { - val currentValue = ctx.get(TOTAL) ?: 0L - val newValue = currentValue + value - ctx.set(TOTAL, newValue) - return CounterUpdate(currentValue, newValue) - } -} - -fun main() { - RestateHttpEndpointBuilder.builder().with(CounterKt()).buildAndListen() -} diff --git a/examples/src/main/resources/log4j2.properties b/examples/src/main/resources/log4j2.properties deleted file mode 100644 index 81a0455a..00000000 --- a/examples/src/main/resources/log4j2.properties +++ /dev/null @@ -1,26 +0,0 @@ -# Set to debug or trace if log4j initialization is failing -status = warn - -# Console appender configuration -appender.console.type = Console -appender.console.name = consoleLogger -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %notEmpty{[%X{restateServiceMethod}]}%notEmpty{[%X{restateInvocationId}]} %c - %m%n - -# Filter out logging during replay -appender.console.filter.replay.type = ContextMapFilter -appender.console.filter.replay.onMatch = DENY -appender.console.filter.replay.onMismatch = NEUTRAL -appender.console.filter.replay.0.type = KeyValuePair -appender.console.filter.replay.0.key = restateInvocationStatus -appender.console.filter.replay.0.value = REPLAYING - -# Restate logs to debug level -logger.app.name = dev.restate -logger.app.level = info -logger.app.additivity = false -logger.app.appenderRef.console.ref = consoleLogger - -# Root logger -rootLogger.level = info -rootLogger.appenderRef.stdout.ref = consoleLogger \ No newline at end of file diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 2d2f8d81..00000000 --- a/gradle.properties +++ /dev/null @@ -1,5 +0,0 @@ -org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index 7e8ac7da..00000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,2 +0,0 @@ -[versions] - restate = '0.9.0-SNAPSHOT' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd491..00000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 1af9e093..00000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100755 index 1aa94a42..00000000 --- a/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright Β© 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β», -# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»; -# * compound commands having a testable exit status, especially Β«caseΒ»; -# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 93e3f59f..00000000 --- a/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/sdk-api-gen-common/build.gradle.kts b/sdk-api-gen-common/build.gradle.kts deleted file mode 100644 index 6d69e5b9..00000000 --- a/sdk-api-gen-common/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` -} - -description = "Restate SDK API Gen Common" - -dependencies { - compileOnly(coreLibs.jspecify) - - api("com.github.jknack:handlebars:4.3.1") - api(project(":sdk-common")) -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Component.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Component.java deleted file mode 100644 index 04939368..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Component.java +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.model; - -import dev.restate.sdk.common.ComponentType; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; - -public class Component { - - private final CharSequence targetPkg; - private final CharSequence targetFqcn; - private final String componentName; - private final ComponentType componentType; - private final List handlers; - - public Component( - CharSequence targetPkg, - CharSequence targetFqcn, - String componentName, - ComponentType componentType, - List handlers) { - this.targetPkg = targetPkg; - this.targetFqcn = targetFqcn; - this.componentName = componentName; - - this.componentType = componentType; - this.handlers = handlers; - } - - public CharSequence getTargetPkg() { - return this.targetPkg; - } - - public CharSequence getTargetFqcn() { - return this.targetFqcn; - } - - public String getFullyQualifiedComponentName() { - return this.componentName; - } - - public String getSimpleComponentName() { - return this.componentName.substring(this.componentName.lastIndexOf('.') + 1); - } - - public CharSequence getGeneratedClassFqcnPrefix() { - if (this.targetPkg == null || this.targetPkg.length() == 0) { - return getSimpleComponentName(); - } - return this.targetPkg + "." + getSimpleComponentName(); - } - - public ComponentType getComponentType() { - return componentType; - } - - public List getMethods() { - return handlers; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private CharSequence targetPkg; - private CharSequence targetFqcn; - private String componentName; - private ComponentType componentType; - private final List handlers = new ArrayList<>(); - - public Builder withTargetPkg(CharSequence targetPkg) { - this.targetPkg = targetPkg; - return this; - } - - public Builder withTargetFqcn(CharSequence targetFqcn) { - this.targetFqcn = targetFqcn; - return this; - } - - public Builder withComponentName(String componentName) { - this.componentName = componentName; - return this; - } - - public Builder withComponentType(ComponentType componentType) { - this.componentType = componentType; - return this; - } - - public Builder withHandlers(Collection handlers) { - this.handlers.addAll(handlers); - return this; - } - - public Builder withHandler(Handler handler) { - this.handlers.add(handler); - return this; - } - - public CharSequence getTargetPkg() { - return targetPkg; - } - - public CharSequence getTargetFqcn() { - return targetFqcn; - } - - public String getComponentName() { - return componentName; - } - - public ComponentType getComponentType() { - return componentType; - } - - public List getHandlers() { - return handlers; - } - - public Component build() { - return new Component( - Objects.requireNonNull(targetPkg), - Objects.requireNonNull(targetFqcn), - Objects.requireNonNull(componentName), - Objects.requireNonNull(componentType), - handlers); - } - } -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java deleted file mode 100644 index 4093b5af..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.model; - -import java.util.Objects; - -public class Handler { - - private final CharSequence name; - private final HandlerType handlerType; - private final PayloadType inputType; - private final PayloadType outputType; - - public Handler( - CharSequence name, HandlerType handlerType, PayloadType inputType, PayloadType outputType) { - this.name = name; - this.handlerType = handlerType; - this.inputType = inputType; - this.outputType = outputType; - } - - public CharSequence getName() { - return name; - } - - public HandlerType getHandlerType() { - return handlerType; - } - - public PayloadType getInputType() { - return inputType; - } - - public PayloadType getOutputType() { - return outputType; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private CharSequence name; - private HandlerType handlerType; - private PayloadType inputType; - private PayloadType outputType; - - public Builder withName(CharSequence name) { - this.name = name; - return this; - } - - public Builder withHandlerType(HandlerType handlerType) { - this.handlerType = handlerType; - return this; - } - - public Builder withInputType(PayloadType inputType) { - this.inputType = inputType; - return this; - } - - public Builder withOutputType(PayloadType outputType) { - this.outputType = outputType; - return this; - } - - public CharSequence getName() { - return name; - } - - public HandlerType getHandlerType() { - return handlerType; - } - - public PayloadType getInputType() { - return inputType; - } - - public PayloadType getOutputType() { - return outputType; - } - - public Handler build() { - return new Handler( - Objects.requireNonNull(name), Objects.requireNonNull(handlerType), inputType, outputType); - } - } -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/HandlerType.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/HandlerType.java deleted file mode 100644 index af8c4288..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/HandlerType.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.model; - -public enum HandlerType { - SHARED, - EXCLUSIVE, - STATELESS, - WORKFLOW; -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/PayloadType.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/PayloadType.java deleted file mode 100644 index 136d8d9d..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/PayloadType.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.model; - -import java.util.Objects; - -public class PayloadType { - - private final boolean isEmpty; - private final String name; - private final String boxed; - private final String serdeDecl; - - public PayloadType(boolean isEmpty, String name, String boxed, String serdeDecl) { - this.isEmpty = isEmpty; - this.name = name; - this.boxed = boxed; - this.serdeDecl = serdeDecl; - } - - public boolean isEmpty() { - return isEmpty; - } - - public String getName() { - return name; - } - - public String getBoxed() { - return boxed; - } - - public String getSerdeDecl() { - return serdeDecl; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - PayloadType that = (PayloadType) o; - - if (isEmpty != that.isEmpty) return false; - if (!Objects.equals(name, that.name)) return false; - if (!Objects.equals(boxed, that.boxed)) return false; - return Objects.equals(serdeDecl, that.serdeDecl); - } - - @Override - public int hashCode() { - return Objects.hash(name, boxed, serdeDecl); - } - - @Override - public String toString() { - return "PayloadType{" - + "name='" - + name - + '\'' - + ", boxed='" - + boxed - + '\'' - + ", serdeDecl='" - + serdeDecl - + '\'' - + '}'; - } -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java deleted file mode 100644 index ca17a9eb..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.template; - -import com.github.jknack.handlebars.Context; -import com.github.jknack.handlebars.Handlebars; -import com.github.jknack.handlebars.Template; -import com.github.jknack.handlebars.context.FieldValueResolver; -import com.github.jknack.handlebars.helper.StringHelpers; -import com.github.jknack.handlebars.io.TemplateLoader; -import dev.restate.sdk.common.ComponentType; -import dev.restate.sdk.common.function.ThrowingFunction; -import dev.restate.sdk.gen.model.Component; -import dev.restate.sdk.gen.model.Handler; -import dev.restate.sdk.gen.model.HandlerType; -import java.io.IOException; -import java.io.Writer; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class HandlebarsTemplateEngine { - - private final String baseTemplateName; - private final Map templates; - - public HandlebarsTemplateEngine( - String baseTemplateName, - TemplateLoader templateLoader, - Map templates) { - this.baseTemplateName = baseTemplateName; - - Handlebars handlebars = new Handlebars(templateLoader); - handlebars.registerHelpers(StringHelpers.class); - - this.templates = - templates.entrySet().stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - e -> { - try { - return handlebars.compile(e.getValue()); - } catch (IOException ex) { - throw new RuntimeException( - "Can't compile template for " - + e.getKey() - + " with base template name " - + baseTemplateName, - ex); - } - })); - } - - public void generate(ThrowingFunction createFile, Component component) - throws Throwable { - String fileName = component.getGeneratedClassFqcnPrefix() + this.baseTemplateName; - try (Writer out = createFile.apply(fileName)) { - this.templates - .get(component.getComponentType()) - .apply( - Context.newBuilder(new ComponentTemplateModel(component, this.baseTemplateName)) - .resolver(FieldValueResolver.INSTANCE) - .build(), - out); - } - } - - // --- classes to interact with the handlebars template - - static class ComponentTemplateModel { - public final String originalClassPkg; - public final String originalClassFqcn; - public final String generatedClassSimpleNamePrefix; - public final String generatedClassSimpleName; - public final String componentName; - public final String componentType; - public final boolean isWorkflow; - public final boolean isObject; - public final boolean isService; - public final List handlers; - - private ComponentTemplateModel(Component inner, String baseTemplateName) { - this.originalClassPkg = inner.getTargetPkg().toString(); - this.originalClassFqcn = inner.getTargetFqcn().toString(); - this.generatedClassSimpleNamePrefix = inner.getSimpleComponentName(); - this.generatedClassSimpleName = this.generatedClassSimpleNamePrefix + baseTemplateName; - this.componentName = inner.getFullyQualifiedComponentName(); - - this.componentType = inner.getComponentType().toString(); - this.isWorkflow = inner.getComponentType() == ComponentType.WORKFLOW; - this.isObject = inner.getComponentType() == ComponentType.VIRTUAL_OBJECT; - this.isService = inner.getComponentType() == ComponentType.SERVICE; - - this.handlers = - inner.getMethods().stream().map(HandlerTemplateModel::new).collect(Collectors.toList()); - } - } - - static class HandlerTemplateModel { - public final String name; - public final String handlerType; - public final boolean isWorkflow; - public final boolean isShared; - public final boolean isStateless; - public final boolean isExclusive; - - public final boolean inputEmpty; - public final String inputFqcn; - public final String inputSerdeDecl; - public final String boxedInputFqcn; - public final String inputSerdeFieldName; - - public final boolean outputEmpty; - public final String outputFqcn; - public final String outputSerdeDecl; - public final String boxedOutputFqcn; - public final String outputSerdeFieldName; - - private HandlerTemplateModel(Handler inner) { - this.name = inner.getName().toString(); - this.handlerType = inner.getHandlerType().toString(); - this.isWorkflow = inner.getHandlerType() == HandlerType.WORKFLOW; - this.isShared = inner.getHandlerType() == HandlerType.SHARED; - this.isExclusive = inner.getHandlerType() == HandlerType.EXCLUSIVE; - this.isStateless = inner.getHandlerType() == HandlerType.STATELESS; - - this.inputEmpty = inner.getInputType().isEmpty(); - this.inputFqcn = inner.getInputType().getName(); - this.inputSerdeDecl = inner.getInputType().getSerdeDecl(); - this.boxedInputFqcn = inner.getInputType().getBoxed(); - this.inputSerdeFieldName = "SERDE_" + this.name.toUpperCase() + "_INPUT"; - - this.outputEmpty = inner.getOutputType().isEmpty(); - this.outputFqcn = inner.getOutputType().getName(); - this.outputSerdeDecl = inner.getOutputType().getSerdeDecl(); - this.boxedOutputFqcn = inner.getOutputType().getBoxed(); - this.outputSerdeFieldName = "SERDE_" + this.name.toUpperCase() + "_OUTPUT"; - } - } -} diff --git a/sdk-api-gen/build.gradle.kts b/sdk-api-gen/build.gradle.kts deleted file mode 100644 index 0b43cfd5..00000000 --- a/sdk-api-gen/build.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - java - application - `library-publishing-conventions` -} - -description = "Restate SDK API Gen" - -dependencies { - compileOnly(coreLibs.jspecify) - - implementation(project(":sdk-api-gen-common")) - - implementation(project(":sdk-api")) - implementation(project(":sdk-workflow-api")) - - testAnnotationProcessor(project(":sdk-api-gen")) - testImplementation(project(":sdk-core")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(coreLibs.protobuf.java) - testImplementation(coreLibs.log4j.core) - testImplementation(platform(jacksonLibs.jackson.bom)) - testImplementation(jacksonLibs.jackson.databind) - testImplementation(project(":sdk-serde-jackson")) - - // Import test suites from sdk-core - testImplementation(project(":sdk-core", "testArchive")) -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java deleted file mode 100644 index 254215dd..00000000 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen; - -import dev.restate.sdk.common.ComponentAdapter; -import dev.restate.sdk.common.ComponentType; -import dev.restate.sdk.common.function.ThrowingFunction; -import dev.restate.sdk.gen.model.Component; -import dev.restate.sdk.gen.template.HandlebarsTemplateEngine; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.*; -import java.util.stream.Collectors; -import javax.annotation.processing.*; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.tools.FileObject; -import javax.tools.StandardLocation; - -@SupportedAnnotationTypes({ - "dev.restate.sdk.annotation.Service", - "dev.restate.sdk.annotation.Workflow", - "dev.restate.sdk.annotation.VirtualObject" -}) -@SupportedSourceVersion(SourceVersion.RELEASE_11) -public class ComponentProcessor extends AbstractProcessor { - - private HandlebarsTemplateEngine serviceAdapterCodegen; - private HandlebarsTemplateEngine clientCodegen; - - @Override - public synchronized void init(ProcessingEnvironment processingEnv) { - super.init(processingEnv); - - FilerTemplateLoader filerTemplateLoader = new FilerTemplateLoader(processingEnv.getFiler()); - - this.serviceAdapterCodegen = - new HandlebarsTemplateEngine( - "ComponentAdapter", - filerTemplateLoader, - Map.of( - ComponentType.WORKFLOW, - "templates/workflow/ComponentAdapter.hbs", - ComponentType.SERVICE, - "templates/ComponentAdapter.hbs", - ComponentType.VIRTUAL_OBJECT, - "templates/ComponentAdapter.hbs")); - this.clientCodegen = - new HandlebarsTemplateEngine( - "Client", - filerTemplateLoader, - Map.of( - ComponentType.WORKFLOW, - "templates/workflow/Client.hbs", - ComponentType.SERVICE, - "templates/Client.hbs", - ComponentType.VIRTUAL_OBJECT, - "templates/Client.hbs")); - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - ElementConverter converter = - new ElementConverter( - processingEnv.getMessager(), - processingEnv.getElementUtils(), - processingEnv.getTypeUtils()); - - // Parsing phase - List> parsedServices = - annotations.stream() - .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream()) - .filter(e -> e.getKind().isClass() || e.getKind().isInterface()) - .map(e -> Map.entry((Element) e, converter.fromTypeElement((TypeElement) e))) - .collect(Collectors.toList()); - - Filer filer = processingEnv.getFiler(); - - // Run code generation - for (Map.Entry e : parsedServices) { - try { - ThrowingFunction fileCreator = - name -> filer.createSourceFile(name, e.getKey()).openWriter(); - this.serviceAdapterCodegen.generate(fileCreator, e.getValue()); - this.clientCodegen.generate(fileCreator, e.getValue()); - } catch (Throwable ex) { - throw new RuntimeException(ex); - } - } - - // META-INF - Path resourceFilePath; - try { - resourceFilePath = - readOrCreateResource( - processingEnv.getFiler(), - "META-INF/services/" + ComponentAdapter.class.getCanonicalName()); - Files.createDirectories(resourceFilePath.getParent()); - } catch (IOException e) { - throw new RuntimeException(e); - } - - try (BufferedWriter writer = - Files.newBufferedWriter( - resourceFilePath, - StandardCharsets.UTF_8, - StandardOpenOption.WRITE, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND)) { - for (Map.Entry e : parsedServices) { - writer.write(e.getValue().getGeneratedClassFqcnPrefix() + "ComponentAdapter"); - writer.write('\n'); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - return false; - } - - public static Path readOrCreateResource(Filer filer, String file) throws IOException { - try { - FileObject fileObject = filer.getResource(StandardLocation.CLASS_OUTPUT, "", file); - return new File(fileObject.toUri()).toPath(); - } catch (IOException e) { - FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", file); - return new File(fileObject.toUri()).toPath(); - } - } -} diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java deleted file mode 100644 index e9c7a7ad..00000000 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen; - -import dev.restate.sdk.Context; -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.annotation.Exclusive; -import dev.restate.sdk.annotation.Shared; -import dev.restate.sdk.annotation.Workflow; -import dev.restate.sdk.common.ComponentType; -import dev.restate.sdk.gen.model.*; -import dev.restate.sdk.workflow.WorkflowContext; -import dev.restate.sdk.workflow.WorkflowSharedContext; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.annotation.processing.Messager; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; - -public class ElementConverter { - - private static final PayloadType EMPTY_PAYLOAD = - new PayloadType(true, "", "Void", "dev.restate.sdk.common.CoreSerdes.VOID"); - - private final Messager messager; - private final Elements elements; - private final Types types; - - public ElementConverter(Messager messager, Elements elements, Types types) { - this.messager = messager; - this.elements = elements; - this.types = types; - } - - public Component fromTypeElement(TypeElement element) { - validateType(element); - - dev.restate.sdk.annotation.Service serviceAnnotation = - element.getAnnotation(dev.restate.sdk.annotation.Service.class); - dev.restate.sdk.annotation.VirtualObject virtualObjectAnnotation = - element.getAnnotation(dev.restate.sdk.annotation.VirtualObject.class); - dev.restate.sdk.annotation.Workflow workflowAnnotation = - element.getAnnotation(dev.restate.sdk.annotation.Workflow.class); - boolean isAnnotatedWithService = serviceAnnotation != null; - boolean isAnnotatedWithVirtualObject = virtualObjectAnnotation != null; - boolean isAnnotatedWithWorkflow = workflowAnnotation != null; - - // Should be guaranteed by the caller - assert isAnnotatedWithWorkflow || isAnnotatedWithVirtualObject || isAnnotatedWithService; - - // Check there's no more than one annotation - if (!Boolean.logicalXor( - isAnnotatedWithService, - Boolean.logicalXor(isAnnotatedWithWorkflow, isAnnotatedWithVirtualObject))) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The type can be annotated only with one annotation between @VirtualObject, @Workflow and @Service", - element); - } - - ComponentType type = - isAnnotatedWithWorkflow - ? ComponentType.WORKFLOW - : isAnnotatedWithService ? ComponentType.SERVICE : ComponentType.VIRTUAL_OBJECT; - - // Infer names - - CharSequence targetPkg = elements.getPackageOf(element).getQualifiedName(); - CharSequence targetFqcn = element.getQualifiedName(); - - String componentName = - isAnnotatedWithService - ? serviceAnnotation.name() - : isAnnotatedWithVirtualObject - ? virtualObjectAnnotation.name() - : workflowAnnotation.name(); - if (componentName.isEmpty()) { - // Use simple class name, flattening subclasses names - componentName = - targetFqcn.toString().substring(targetPkg.length()).replaceAll(Pattern.quote("."), ""); - } - - // Compute handlers - List handlers = - elements.getAllMembers(element).stream() - .filter(e -> e instanceof ExecutableElement) - .filter( - e -> - e.getAnnotation(dev.restate.sdk.annotation.Handler.class) != null - || e.getAnnotation(Workflow.class) != null - || e.getAnnotation(Exclusive.class) != null - || e.getAnnotation(Shared.class) != null) - .map(e -> fromExecutableElement(type, ((ExecutableElement) e))) - .collect(Collectors.toList()); - validateHandlers(type, handlers, element); - - if (handlers.isEmpty()) { - messager.printMessage( - Diagnostic.Kind.WARNING, "The component " + componentName + " has no handlers", element); - } - - return new Component.Builder() - .withTargetPkg(targetPkg) - .withTargetFqcn(targetFqcn) - .withComponentName(componentName) - .withComponentType(type) - .withHandlers(handlers) - .build(); - } - - private void validateType(TypeElement element) { - if (!element.getTypeParameters().isEmpty()) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The ComponentProcessor doesn't support components with generics", - element); - } - if (element.getKind().equals(ElementKind.ENUM)) { - messager.printMessage( - Diagnostic.Kind.ERROR, "The EntityProcessor doesn't support enums", element); - } - - if (element.getModifiers().contains(Modifier.PRIVATE)) { - messager.printMessage(Diagnostic.Kind.ERROR, "The annotated class is private", element); - } - } - - private void validateHandlers( - ComponentType componentType, List handlers, TypeElement element) { - // Additional validation for Workflow types - if (componentType.equals(ComponentType.WORKFLOW)) { - if (handlers.stream().filter(m -> m.getHandlerType().equals(HandlerType.WORKFLOW)).count() - != 1) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "Workflow services must have exactly one method annotated as @Workflow", - element); - } - } - } - - private Handler fromExecutableElement(ComponentType componentType, ExecutableElement element) { - if (!element.getTypeParameters().isEmpty()) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The EntityProcessor doesn't support methods with generics", - element); - } - if (element.getKind().equals(ElementKind.CONSTRUCTOR)) { - messager.printMessage( - Diagnostic.Kind.ERROR, "You cannot annotate a constructor as Restate method"); - } - if (element.getKind().equals(ElementKind.STATIC_INIT)) { - messager.printMessage( - Diagnostic.Kind.ERROR, "You cannot annotate a static init as Restate method"); - } - - boolean isAnnotatedWithShared = element.getAnnotation(Shared.class) != null; - boolean isAnnotatedWithExclusive = element.getAnnotation(Exclusive.class) != null; - boolean isAnnotatedWithWorkflow = element.getAnnotation(Workflow.class) != null; - - // Check there's no more than one annotation - boolean hasAnyAnnotation = - isAnnotatedWithExclusive || isAnnotatedWithShared || isAnnotatedWithWorkflow; - boolean hasExactlyOneAnnotation = - Boolean.logicalXor( - isAnnotatedWithShared, - Boolean.logicalXor(isAnnotatedWithWorkflow, isAnnotatedWithExclusive)); - if (!(!hasAnyAnnotation || hasExactlyOneAnnotation)) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "You can have only one annotation between @Shared, @Exclusive and @Workflow to a method", - element); - } - - HandlerType handlerType = - isAnnotatedWithWorkflow - ? HandlerType.WORKFLOW - : isAnnotatedWithShared - ? HandlerType.SHARED - : isAnnotatedWithExclusive - ? HandlerType.EXCLUSIVE - : defaultHandlerType(componentType, element); - - validateMethodSignature(componentType, handlerType, element); - - return new Handler.Builder() - .withName(element.getSimpleName()) - .withHandlerType(handlerType) - .withInputType( - element.getParameters().size() > 1 - ? payloadFromType(element.getParameters().get(1).asType()) - : EMPTY_PAYLOAD) - .withOutputType( - !element.getReturnType().getKind().equals(TypeKind.VOID) - ? payloadFromType(element.getReturnType()) - : EMPTY_PAYLOAD) - .build(); - } - - private HandlerType defaultHandlerType(ComponentType componentType, ExecutableElement element) { - switch (componentType) { - case SERVICE: - return HandlerType.STATELESS; - case VIRTUAL_OBJECT: - return HandlerType.EXCLUSIVE; - case WORKFLOW: - messager.printMessage( - Diagnostic.Kind.ERROR, - "Workflow methods MUST be annotated with either @Shared or @Workflow", - element); - } - throw new IllegalStateException("Unexpected"); - } - - private void validateMethodSignature( - ComponentType componentType, HandlerType handlerType, ExecutableElement element) { - switch (handlerType) { - case SHARED: - if (componentType == ComponentType.WORKFLOW) { - validateFirstParameterType(WorkflowSharedContext.class, element); - } else { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The annotation @Shared is not supported by the service type " + componentType, - element); - } - break; - case EXCLUSIVE: - if (componentType == ComponentType.VIRTUAL_OBJECT) { - validateFirstParameterType(ObjectContext.class, element); - } else { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The annotation @Exclusive is not supported by the service type " + componentType, - element); - } - break; - case STATELESS: - validateFirstParameterType(Context.class, element); - break; - case WORKFLOW: - if (componentType == ComponentType.WORKFLOW) { - validateFirstParameterType(WorkflowContext.class, element); - } else { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The annotation @Shared is not supported by the service type " + componentType, - element); - } - break; - } - } - - private void validateFirstParameterType(Class clazz, ExecutableElement element) { - if (!types.isSameType( - element.getParameters().get(0).asType(), - elements.getTypeElement(clazz.getCanonicalName()).asType())) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The method signature must have " + clazz.getCanonicalName() + " as first parameter", - element); - } - } - - private PayloadType payloadFromType(TypeMirror typeMirror) { - Objects.requireNonNull(typeMirror); - return new PayloadType( - false, typeMirror.toString(), boxedType(typeMirror), serdeDecl(typeMirror)); - } - - private static String serdeDecl(TypeMirror ty) { - switch (ty.getKind()) { - case BOOLEAN: - return "dev.restate.sdk.common.CoreSerdes.JSON_BOOLEAN"; - case BYTE: - return "dev.restate.sdk.common.CoreSerdes.JSON_BYTE"; - case SHORT: - return "dev.restate.sdk.common.CoreSerdes.JSON_SHORT"; - case INT: - return "dev.restate.sdk.common.CoreSerdes.JSON_INT"; - case LONG: - return "dev.restate.sdk.common.CoreSerdes.JSON_LONG"; - case CHAR: - return "dev.restate.sdk.common.CoreSerdes.JSON_CHAR"; - case FLOAT: - return "dev.restate.sdk.common.CoreSerdes.JSON_FLOAT"; - case DOUBLE: - return "dev.restate.sdk.common.CoreSerdes.JSON_DOUBLE"; - case VOID: - return "dev.restate.sdk.common.CoreSerdes.VOID"; - default: - // Default to Jackson type reference serde - return "dev.restate.sdk.serde.jackson.JacksonSerdes.of(new com.fasterxml.jackson.core.type.TypeReference<" - + ty - + ">() {})"; - } - } - - private static String boxedType(TypeMirror ty) { - switch (ty.getKind()) { - case BOOLEAN: - return "Boolean"; - case BYTE: - return "Byte"; - case SHORT: - return "Short"; - case INT: - return "Integer"; - case LONG: - return "Long"; - case CHAR: - return "Char"; - case FLOAT: - return "Float"; - case DOUBLE: - return "Double"; - case VOID: - return "Void"; - default: - return ty.toString(); - } - } -} diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/FilerTemplateLoader.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/FilerTemplateLoader.java deleted file mode 100644 index ef6707d1..00000000 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/FilerTemplateLoader.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen; - -import com.github.jknack.handlebars.io.AbstractTemplateLoader; -import com.github.jknack.handlebars.io.TemplateSource; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.nio.file.Paths; -import javax.annotation.processing.Filer; -import javax.tools.StandardLocation; - -/** - * We need this because the built-in ClassLoaderTemplateLoader is not reliable in the annotation - * processor context - */ -class FilerTemplateLoader extends AbstractTemplateLoader { - private final Filer filer; - - public FilerTemplateLoader(Filer filer) { - this.filer = filer; - } - - @Override - public TemplateSource sourceAt(String location) { - Path path = Paths.get(location); - return new TemplateSource() { - @Override - public String content(Charset charset) throws IOException { - return filer - .getResource( - StandardLocation.ANNOTATION_PROCESSOR_PATH, - path.getParent().toString().replace('/', '.'), - path.getFileName().toString()) - .getCharContent(true) - .toString(); - } - - @Override - public String filename() { - return "/" + location; - } - - @Override - public long lastModified() { - return 0; - } - }; - } -} diff --git a/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 93da2a05..00000000 --- a/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -dev.restate.sdk.gen.ComponentProcessor \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/Client.hbs b/sdk-api-gen/src/main/resources/templates/Client.hbs deleted file mode 100644 index d90dff2f..00000000 --- a/sdk-api-gen/src/main/resources/templates/Client.hbs +++ /dev/null @@ -1,121 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -import dev.restate.sdk.Awaitable; -import dev.restate.sdk.Context; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.Target; -import java.util.Optional; -import java.time.Duration; - -public class {{generatedClassSimpleName}} { - - public static final String COMPONENT_NAME = "{{componentName}}"; - - {{#handlers}} - private static final Serde<{{{boxedInputFqcn}}}> {{inputSerdeFieldName}} = {{{inputSerdeDecl}}}; - private static final Serde<{{{boxedOutputFqcn}}}> {{outputSerdeFieldName}} = {{{outputSerdeDecl}}}; - {{/handlers}} - - public static ContextClient fromContext(Context ctx{{#isObject}}, String key{{/isObject}}) { - return new ContextClient(ctx{{#isObject}}, key{{/isObject}}); - } - - public static IngressClient fromIngress(dev.restate.sdk.client.IngressClient ingressClient{{#isObject}}, String key{{/isObject}}) { - return new IngressClient(ingressClient{{#isObject}}, key{{/isObject}}); - } - - public static IngressClient fromIngress(String baseUri{{#isObject}}, String key{{/isObject}}) { - return new IngressClient(dev.restate.sdk.client.IngressClient.defaultClient(baseUri){{#isObject}}, key{{/isObject}}); - } - - public static class ContextClient { - - private final Context ctx; - {{#isObject}}private final String key;{{/isObject}} - - public ContextClient(Context ctx{{#isObject}}, String key{{/isObject}}) { - this.ctx = ctx; - {{#isObject}}this.key = key;{{/isObject}} - } - - {{#handlers}} - public Awaitable<{{{boxedOutputFqcn}}}> {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return this.ctx.call( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/handlers}} - - public Send send() { - return new Send(); - } - - public SendDelayed sendDelayed(Duration delay) { - return new SendDelayed(delay); - } - - public class Send { - {{#handlers}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - ContextClient.this.ctx.send( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, ContextClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/handlers}} - } - - public class SendDelayed { - - private final Duration delay; - - SendDelayed(Duration delay) { - this.delay = delay; - } - - {{#handlers}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - ContextClient.this.ctx.sendDelayed( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, ContextClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}, - this.delay); - }{{/handlers}} - } - } - - public static class IngressClient { - - private final dev.restate.sdk.client.IngressClient ingressClient; - {{#isObject}}private final String key;{{/isObject}} - - public IngressClient(dev.restate.sdk.client.IngressClient ingressClient{{#isObject}}, String key{{/isObject}}) { - this.ingressClient = ingressClient; - {{#isObject}}this.key = key;{{/isObject}} - } - - {{#handlers}} - public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - {{^outputEmpty}}return {{/outputEmpty}}this.ingressClient.call( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/handlers}} - - public Send send() { - return new Send(); - } - - public class Send { - {{#handlers}} - public String {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return IngressClient.this.ingressClient.send( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, IngressClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/handlers}} - } - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/ComponentAdapter.hbs b/sdk-api-gen/src/main/resources/templates/ComponentAdapter.hbs deleted file mode 100644 index 0919efdc..00000000 --- a/sdk-api-gen/src/main/resources/templates/ComponentAdapter.hbs +++ /dev/null @@ -1,30 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -public class {{generatedClassSimpleName}} implements dev.restate.sdk.common.ComponentAdapter<{{originalClassFqcn}}> { - - public static final String COMPONENT_NAME = "{{componentName}}"; - - @java.lang.Override - public dev.restate.sdk.common.BindableComponent adapt({{originalClassFqcn}} bindableComponent) { - return dev.restate.sdk.Component.{{#if isObject}}virtualObject{{else}}service{{/if}}(COMPONENT_NAME) - {{#handlers}} - .with( - dev.restate.sdk.Component.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), - (ctx, req) -> { - {{#if outputEmpty}} - {{#if inputEmpty}}bindableComponent.{{name}}(ctx){{else}}bindableComponent.{{name}}(ctx, req){{/if}}; - return null; - {{else}} - return {{#if inputEmpty}}bindableComponent.{{name}}(ctx){{else}}bindableComponent.{{name}}(ctx, req){{/if}}; - {{/if}} - }) - {{/handlers}} - .build(); - } - - @java.lang.Override - public boolean supportsObject(Object serviceObject) { - return serviceObject instanceof {{originalClassFqcn}}; - } - -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs b/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs deleted file mode 100644 index cf704374..00000000 --- a/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs +++ /dev/null @@ -1,148 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -import dev.restate.sdk.Awaitable; -import dev.restate.sdk.Context; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.workflow.impl.WorkflowCodegenUtil; -import java.util.Optional; -import java.time.Duration; - -public class {{generatedClassSimpleName}} { - - public static final String WORKFLOW_NAME = "{{componentName}}"; - - {{#handlers}} - private static final Serde<{{{boxedInputFqcn}}}> {{inputSerdeFieldName}} = {{{inputSerdeDecl}}}; - private static final Serde<{{{boxedOutputFqcn}}}> {{outputSerdeFieldName}} = {{{outputSerdeDecl}}}; - {{/handlers}} - - public static ContextClient fromContext(Context ctx, String key) { - return new ContextClient(ctx, key); - } - - public static IngressClient fromIngress(dev.restate.sdk.client.IngressClient ingressClient, String key) { - return new IngressClient(ingressClient, key); - } - - public static IngressClient fromIngress(String baseUri, String key) { - return new IngressClient(dev.restate.sdk.client.IngressClient.defaultClient(baseUri), key); - } - - public static class ContextClient { - - private final Context ctx; - private final String workflowKey; - - public ContextClient(Context ctx, String workflowKey) { - this.ctx = ctx; - this.workflowKey = workflowKey; - } - - {{#handlers}}{{#if isWorkflow}} - public Awaitable submit({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return WorkflowCodegenUtil.RestateClient.submit(ctx, WORKFLOW_NAME, workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}); - } - - public Awaitable isCompleted() { - return WorkflowCodegenUtil.RestateClient.isCompleted(ctx, WORKFLOW_NAME, workflowKey); - } - - {{^outputEmpty}} - public Awaitable> getOutput() { - return WorkflowCodegenUtil.RestateClient.getOutput(ctx, WORKFLOW_NAME, workflowKey, {{outputSerdeFieldName}}); - }{{/outputEmpty}} - {{/if}}{{/handlers}} - - public Awaitable> getState(StateKey key) { - return WorkflowCodegenUtil.RestateClient.getState(ctx, WORKFLOW_NAME, workflowKey, key); - } - - {{#handlers}}{{#if isShared}} - public Awaitable<{{{boxedOutputFqcn}}}> {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return WorkflowCodegenUtil.RestateClient.invokeShared(ctx, WORKFLOW_NAME, "{{name}}", workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}, {{outputSerdeFieldName}}); - } - {{/if}}{{/handlers}} - - public Send send() { - return new Send(); - } - - public SendDelayed sendDelayed(Duration delay) { - return new SendDelayed(delay); - } - - public class Send { - - {{#handlers}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.RestateClient.invokeSharedSend(ContextClient.this.ctx, WORKFLOW_NAME, "{{name}}", ContextClient.this.workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/if}}{{/handlers}} - - } - - public class SendDelayed { - - private final Duration delay; - - SendDelayed(Duration delay) { - this.delay = delay; - } - - {{#handlers}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.RestateClient.invokeSharedSendDelayed(ContextClient.this.ctx, WORKFLOW_NAME, "{{name}}", ContextClient.this.workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}, delay); - }{{/if}}{{/handlers}} - } - } - - public static class IngressClient { - - private final dev.restate.sdk.client.IngressClient ingressClient; - private final String workflowKey; - - public IngressClient(dev.restate.sdk.client.IngressClient ingressClient, String workflowKey) { - this.ingressClient = ingressClient; - this.workflowKey = workflowKey; - } - - {{#handlers}}{{#if isWorkflow}} - public dev.restate.sdk.workflow.WorkflowExecutionState submit({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return WorkflowCodegenUtil.ExternalClient.submit(ingressClient, WORKFLOW_NAME, workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}); - } - - public boolean isCompleted() { - return WorkflowCodegenUtil.ExternalClient.isCompleted(ingressClient, WORKFLOW_NAME, workflowKey); - } - - {{^outputEmpty}} - public Optional<{{{outputFqcn}}}> getOutput() { - return WorkflowCodegenUtil.ExternalClient.getOutput(ingressClient, WORKFLOW_NAME, workflowKey, {{outputSerdeFieldName}}); - }{{/outputEmpty}} - {{/if}}{{/handlers}} - - public Optional getState(StateKey key) { - return WorkflowCodegenUtil.ExternalClient.getState(ingressClient, WORKFLOW_NAME, workflowKey, key); - } - - {{#handlers}}{{#if isShared}} - public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - {{^outputEmpty}}return {{/outputEmpty}}WorkflowCodegenUtil.ExternalClient.invokeShared(IngressClient.this.ingressClient, WORKFLOW_NAME, "{{name}}", IngressClient.this.workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}, {{outputSerdeFieldName}}); - } - {{/if}}{{/handlers}} - - public Send send() { - return new Send(); - } - - public class Send { - - {{#handlers}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.ExternalClient.invokeSharedSend(IngressClient.this.ingressClient, WORKFLOW_NAME, "{{name}}", IngressClient.this.workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}); - } - {{/if}}{{/handlers}} - - } - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/workflow/ComponentAdapter.hbs b/sdk-api-gen/src/main/resources/templates/workflow/ComponentAdapter.hbs deleted file mode 100644 index 2fa54aae..00000000 --- a/sdk-api-gen/src/main/resources/templates/workflow/ComponentAdapter.hbs +++ /dev/null @@ -1,42 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -public class {{generatedClassSimpleName}} implements dev.restate.sdk.common.ComponentAdapter<{{originalClassFqcn}}> { - - public static final String COMPONENT_NAME = "{{componentName}}"; - - @java.lang.Override - public dev.restate.sdk.common.BindableComponent adapt({{originalClassFqcn}} bindableComponent) { - return dev.restate.sdk.workflow.WorkflowBuilder.named( - COMPONENT_NAME, - {{#handlers}}{{#if isWorkflow}} - dev.restate.sdk.Component.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), - (ctx, req) -> { - {{#if outputEmpty}} - {{#if inputEmpty}}bindableComponent.{{name}}(ctx){{else}}bindableComponent.{{name}}(ctx, req){{/if}}; - return null; - {{else}} - return {{#if inputEmpty}}bindableComponent.{{name}}(ctx){{else}}bindableComponent.{{name}}(ctx, req){{/if}}; - {{/if}} - } - {{/if}}{{/handlers}}) - {{#handlers}}{{#if isShared}} - .with{{capitalizeFirst (lower handlerType)}}( - dev.restate.sdk.Component.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), - (ctx, req) -> { - {{#if outputEmpty}} - {{#if inputEmpty}}bindableComponent.{{name}}(ctx){{else}}bindableComponent.{{name}}(ctx, req){{/if}}; - return null; - {{else}} - return {{#if inputEmpty}}bindableComponent.{{name}}(ctx){{else}}bindableComponent.{{name}}(ctx, req){{/if}}; - {{/if}} - }) - {{/if}}{{/handlers}} - .build(); - } - - @java.lang.Override - public boolean supportsObject(Object serviceObject) { - return serviceObject instanceof {{originalClassFqcn}}; - } - -} \ No newline at end of file diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java b/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java deleted file mode 100644 index 5d0654bd..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.testInvocation; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.annotation.Exclusive; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; -import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Target; -import dev.restate.sdk.core.ProtoUtils; -import dev.restate.sdk.core.TestDefinitions; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import java.util.stream.Stream; - -public class CodegenTest implements TestSuite { - - @Service - static class ServiceGreeter { - @Handler - String greet(Context context, String request) { - return request; - } - } - - @VirtualObject - static class ObjectGreeter { - @Exclusive - String greet(ObjectContext context, String request) { - return request; - } - } - - @VirtualObject - public interface GreeterInterface { - @Exclusive - String greet(ObjectContext context, String request); - } - - private static class ObjectGreeterImplementedFromInterface implements GreeterInterface { - - @Override - public String greet(ObjectContext context, String request) { - return request; - } - } - - @Service(name = "Empty") - static class Empty { - - @Handler - public String emptyInput(Context context) { - var client = EmptyClient.fromContext(context); - return client.emptyInput().await(); - } - - @Handler - public void emptyOutput(Context context, String request) { - var client = EmptyClient.fromContext(context); - client.emptyOutput(request).await(); - } - - @Handler - public void emptyInputOutput(Context context) { - var client = EmptyClient.fromContext(context); - client.emptyInputOutput().await(); - } - } - - @Service(name = "PrimitiveTypes") - static class PrimitiveTypes { - - @Handler - public int primitiveOutput(Context context) { - var client = PrimitiveTypesClient.fromContext(context); - return client.primitiveOutput().await(); - } - - @Handler - public void primitiveInput(Context context, int input) { - var client = PrimitiveTypesClient.fromContext(context); - client.primitiveInput(input).await(); - } - } - - @Override - public Stream definitions() { - return Stream.of( - testInvocation(ServiceGreeter::new, "greet") - .withInput(startMessage(1), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation(ObjectGreeter::new, "greet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation(ObjectGreeterImplementedFromInterface::new, "greet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation(Empty::new, "emptyInput") - .withInput(startMessage(1), inputMessage(), completionMessage(1, "Till")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyInput")), - outputMessage("Till"), - END_MESSAGE) - .named("empty output"), - testInvocation(Empty::new, "emptyOutput") - .withInput( - startMessage(1), - inputMessage("Francesco"), - completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyOutput"), "Francesco"), - ProtoUtils.outputMessage(), - END_MESSAGE) - .named("empty output"), - testInvocation(Empty::new, "emptyInputOutput") - .withInput( - startMessage(1), - inputMessage("Francesco"), - completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyInputOutput")), - ProtoUtils.outputMessage(), - END_MESSAGE) - .named("empty input and empty output"), - testInvocation(PrimitiveTypes::new, "primitiveOutput") - .withInput( - startMessage(1), inputMessage(), completionMessage(1, CoreSerdes.JSON_INT, 10)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage( - Target.service("PrimitiveTypes", "primitiveOutput"), CoreSerdes.VOID, null), - outputMessage(CoreSerdes.JSON_INT, 10), - END_MESSAGE) - .named("primitive output"), - testInvocation(PrimitiveTypes::new, "primitiveInput") - .withInput( - startMessage(1), inputMessage(10), completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage( - Target.service("PrimitiveTypes", "primitiveInput"), CoreSerdes.JSON_INT, 10), - outputMessage(), - END_MESSAGE) - .named("primitive input")); - } -} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithExplicitName.java b/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithExplicitName.java deleted file mode 100644 index b3fda9b2..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithExplicitName.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; - -@Service(name = "MyExplicitName") -public interface GreeterWithExplicitName { - @Handler - String greet(Context context, String request); -} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithoutExplicitName.java b/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithoutExplicitName.java deleted file mode 100644 index 56d6b9c0..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithoutExplicitName.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; - -@Service -public interface GreeterWithoutExplicitName { - @Handler - String greet(Context context, String request); -} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java b/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java deleted file mode 100644 index 039c56d2..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.core.MockMultiThreaded; -import dev.restate.sdk.core.MockSingleThread; -import dev.restate.sdk.core.TestDefinitions.TestExecutor; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import dev.restate.sdk.core.TestRunner; -import java.util.stream.Stream; - -public class JavaCodegenTests extends TestRunner { - - @Override - protected Stream executors() { - return Stream.of(MockSingleThread.INSTANCE, MockMultiThreaded.INSTANCE); - } - - @Override - public Stream definitions() { - return Stream.of(new CodegenTest()); - } -} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/NameInferenceTest.java b/sdk-api-gen/src/test/java/dev/restate/sdk/NameInferenceTest.java deleted file mode 100644 index ab20cff0..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/NameInferenceTest.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; - -public class NameInferenceTest { - - @Test - void expectedName() { - assertThat(CodegenTestServiceGreeterClient.COMPONENT_NAME) - .isEqualTo("CodegenTestServiceGreeter"); - assertThat(GreeterWithoutExplicitNameClient.COMPONENT_NAME) - .isEqualTo("GreeterWithoutExplicitName"); - assertThat(MyExplicitNameClient.COMPONENT_NAME).isEqualTo("MyExplicitName"); - } -} diff --git a/sdk-api-kotlin-gen/build.gradle.kts b/sdk-api-kotlin-gen/build.gradle.kts deleted file mode 100644 index fe64cad9..00000000 --- a/sdk-api-kotlin-gen/build.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - java - kotlin("jvm") - `library-publishing-conventions` - alias(kotlinLibs.plugins.ksp) -} - -description = "Restate SDK API Kotlin Gen" - -dependencies { - compileOnly(coreLibs.jspecify) - - implementation(kotlinLibs.symbol.processing.api) - implementation(project(":sdk-api-gen-common")) - - implementation(project(":sdk-api-kotlin")) - - kspTest(project(":sdk-api-kotlin-gen")) - testImplementation(project(":sdk-core")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(coreLibs.protobuf.java) - testImplementation(coreLibs.log4j.core) - - // Import test suites from sdk-core - testImplementation(project(":sdk-core", "testArchive")) -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt deleted file mode 100644 index 52a9db93..00000000 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin.gen - -import com.github.jknack.handlebars.io.ClassPathTemplateLoader -import com.google.devtools.ksp.containingFile -import com.google.devtools.ksp.processing.* -import com.google.devtools.ksp.symbol.KSAnnotated -import com.google.devtools.ksp.symbol.Origin -import dev.restate.sdk.common.ComponentAdapter -import dev.restate.sdk.common.ComponentType -import dev.restate.sdk.gen.model.Component -import dev.restate.sdk.gen.template.HandlebarsTemplateEngine -import java.io.BufferedWriter -import java.io.IOException -import java.io.Writer -import java.nio.charset.Charset - -class ComponentProcessor(private val logger: KSPLogger, private val codeGenerator: CodeGenerator) : - SymbolProcessor { - - private val serviceAdapterCodegen: HandlebarsTemplateEngine = - HandlebarsTemplateEngine( - "ComponentAdapter", - ClassPathTemplateLoader(), - mapOf( - ComponentType.SERVICE to "templates/ComponentAdapter", - ComponentType.VIRTUAL_OBJECT to "templates/ComponentAdapter")) - private val clientCodegen: HandlebarsTemplateEngine = - HandlebarsTemplateEngine( - "Client", - ClassPathTemplateLoader(), - mapOf( - ComponentType.SERVICE to "templates/Client", - ComponentType.VIRTUAL_OBJECT to "templates/Client")) - - override fun process(resolver: Resolver): List { - val converter = KElementConverter(logger, resolver.builtIns) - - val resolved = - resolver - .getSymbolsWithAnnotation(dev.restate.sdk.annotation.Service::class.qualifiedName!!) - .toSet() + - resolver - .getSymbolsWithAnnotation( - dev.restate.sdk.annotation.VirtualObject::class.qualifiedName!!) - .toSet() + - resolver - .getSymbolsWithAnnotation( - dev.restate.sdk.annotation.Workflow::class.qualifiedName!!) - .toSet() - - val components = - resolved - .filter { it.containingFile!!.origin == Origin.KOTLIN } - .map { - val componentBuilder = Component.builder() - converter.visitAnnotated(it, componentBuilder) - (it to componentBuilder.build()!!) - } - .toList() - - // Run code generation - for (component in components) { - try { - val fileCreator: (String) -> Writer = { name: String -> - codeGenerator - .createNewFile( - Dependencies(false, component.first.containingFile!!), - component.second.targetPkg.toString(), - name) - .writer(Charset.defaultCharset()) - } - this.serviceAdapterCodegen.generate(fileCreator, component.second) - this.clientCodegen.generate(fileCreator, component.second) - } catch (ex: Throwable) { - throw RuntimeException(ex) - } - } - - // META-INF - if (components.isNotEmpty()) { - generateMetaINF(components) - } - - return emptyList() - } - - private fun generateMetaINF(components: List>) { - val resourceFile = "META-INF/services/${ComponentAdapter::class.java.canonicalName}" - val dependencies = - Dependencies(true, *(components.map { it.first.containingFile!! }.toTypedArray())) - - val writer: BufferedWriter = - try { - codeGenerator.createNewFileByPath(dependencies, resourceFile, "").bufferedWriter() - } catch (e: FileSystemException) { - val existingFile = e.file - val currentValues = existingFile.readText() - val newWriter = e.file.bufferedWriter() - newWriter.write(currentValues) - newWriter - } - - try { - writer.use { - for (component in components) { - it.write("${component.second.generatedClassFqcnPrefix}ComponentAdapter") - it.newLine() - } - } - } catch (e: IOException) { - logger.error("Unable to create $resourceFile: $e") - } - } -} diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessorProvider.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessorProvider.kt deleted file mode 100644 index 7103d0ae..00000000 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessorProvider.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin.gen - -import com.google.devtools.ksp.processing.SymbolProcessor -import com.google.devtools.ksp.processing.SymbolProcessorEnvironment -import com.google.devtools.ksp.processing.SymbolProcessorProvider - -class ComponentProcessorProvider : SymbolProcessorProvider { - - override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { - return ComponentProcessor( - logger = environment.logger, codeGenerator = environment.codeGenerator) - } -} diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt deleted file mode 100644 index 37f56a5c..00000000 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin.gen - -import com.google.devtools.ksp.KspExperimental -import com.google.devtools.ksp.getAnnotationsByType -import com.google.devtools.ksp.getVisibility -import com.google.devtools.ksp.isAnnotationPresent -import com.google.devtools.ksp.processing.KSBuiltIns -import com.google.devtools.ksp.processing.KSPLogger -import com.google.devtools.ksp.symbol.* -import com.google.devtools.ksp.visitor.KSDefaultVisitor -import dev.restate.sdk.common.ComponentType -import dev.restate.sdk.gen.model.Component -import dev.restate.sdk.gen.model.Handler -import dev.restate.sdk.gen.model.HandlerType -import dev.restate.sdk.gen.model.PayloadType -import dev.restate.sdk.kotlin.Context -import dev.restate.sdk.kotlin.ObjectContext -import java.util.regex.Pattern -import kotlin.reflect.KClass - -class KElementConverter(private val logger: KSPLogger, private val builtIns: KSBuiltIns) : - KSDefaultVisitor() { - companion object { - private val SUPPORTED_CLASS_KIND: Set = setOf(ClassKind.CLASS, ClassKind.INTERFACE) - private val EMPTY_PAYLOAD: PayloadType = - PayloadType(true, "", "Unit", "dev.restate.sdk.kotlin.KtSerdes.UNIT") - } - - override fun defaultHandler(node: KSNode, data: Component.Builder) {} - - override fun visitAnnotated(annotated: KSAnnotated, data: Component.Builder) { - if (annotated !is KSClassDeclaration) { - logger.error( - "Only classes or interfaces can be annotated with @Service or @VirtualObject or @Workflow") - } - visitClassDeclaration(annotated as KSClassDeclaration, data) - } - - @OptIn(KspExperimental::class) - override fun visitClassDeclaration( - classDeclaration: KSClassDeclaration, - data: Component.Builder - ) { - // Validate class declaration - if (classDeclaration.typeParameters.isNotEmpty()) { - logger.error( - "The ComponentProcessor doesn't support components with generics", classDeclaration) - } - if (!SUPPORTED_CLASS_KIND.contains(classDeclaration.classKind)) { - logger.error( - "The ComponentProcessor supports only class declarations of kind $SUPPORTED_CLASS_KIND", - classDeclaration) - } - if (classDeclaration.getVisibility() == Visibility.PRIVATE) { - logger.error("The annotated class is private", classDeclaration) - } - if (classDeclaration.isAnnotationPresent(dev.restate.sdk.annotation.Workflow::class)) { - logger.error("sdk-api-kotlin doesn't support workflows yet", classDeclaration) - } - - // Figure out component type annotations - val serviceAnnotation = - classDeclaration - .getAnnotationsByType(dev.restate.sdk.annotation.Service::class) - .firstOrNull() - val virtualObjectAnnotation = - classDeclaration - .getAnnotationsByType(dev.restate.sdk.annotation.VirtualObject::class) - .firstOrNull() - val isAnnotatedWithService = serviceAnnotation != null - val isAnnotatedWithVirtualObject = virtualObjectAnnotation != null - - // Check there's exactly one annotation - if (!(isAnnotatedWithService xor isAnnotatedWithVirtualObject)) { - logger.error( - "The type can be annotated only with one annotation between @VirtualObject and @Service", - classDeclaration) - } - - data.withComponentType( - if (isAnnotatedWithService) ComponentType.SERVICE else ComponentType.VIRTUAL_OBJECT) - - // Infer names - val targetPkg = classDeclaration.packageName.asString() - val targetFqcn = classDeclaration.qualifiedName!!.asString() - var componentName = - if (isAnnotatedWithService) serviceAnnotation!!.name else virtualObjectAnnotation!!.name - if (componentName.isEmpty()) { - // Use Simple class name - // With this logic we make sure we flatten subclasses names - componentName = - targetFqcn.substring(targetPkg.length).replace(Pattern.quote(".").toRegex(), "") - } - data.withTargetPkg(targetPkg).withTargetFqcn(targetFqcn).withComponentName(componentName) - - // Compute handlers - classDeclaration - .getAllFunctions() - .filter { - it.isAnnotationPresent(dev.restate.sdk.annotation.Handler::class) || - it.isAnnotationPresent(dev.restate.sdk.annotation.Workflow::class) || - it.isAnnotationPresent(dev.restate.sdk.annotation.Exclusive::class) || - it.isAnnotationPresent(dev.restate.sdk.annotation.Shared::class) - } - .forEach { visitFunctionDeclaration(it, data) } - - if (data.handlers.isEmpty()) { - logger.warn( - "The class declaration $targetFqcn has no methods annotated as handlers", - classDeclaration) - } - } - - @OptIn(KspExperimental::class) - override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Component.Builder) { - // Validate function declaration - if (function.typeParameters.isNotEmpty()) { - logger.error("The ComponentProcessor doesn't support methods with generics", function) - } - if (function.functionKind != FunctionKind.MEMBER) { - logger.error("Only member function declarations are supported as Restate handlers") - } - if (function.isAnnotationPresent(dev.restate.sdk.annotation.Workflow::class)) { - logger.error("sdk-api-kotlin doesn't support workflows yet", function) - } - - val isAnnotatedWithShared = - function.isAnnotationPresent(dev.restate.sdk.annotation.Service::class) - val isAnnotatedWithExclusive = - function.isAnnotationPresent(dev.restate.sdk.annotation.Exclusive::class) - - // Check there's no more than one annotation - val hasAnyAnnotation = isAnnotatedWithExclusive || isAnnotatedWithShared - val hasExactlyOneAnnotation = isAnnotatedWithShared xor isAnnotatedWithExclusive - if (!(!hasAnyAnnotation || hasExactlyOneAnnotation)) { - logger.error( - "You can have only one annotation between @Shared and @Exclusive to a method", function) - } - - val handlerBuilder = Handler.builder() - - // Set handler type - val handlerType = - if (isAnnotatedWithShared) HandlerType.SHARED - else if (isAnnotatedWithExclusive) HandlerType.EXCLUSIVE - else defaultHandlerType(data.componentType, function) - handlerBuilder.withHandlerType(handlerType) - - validateMethodSignature(data.componentType, handlerType, function) - - data.withHandler( - handlerBuilder - .withName(function.simpleName.asString()) - .withHandlerType(handlerType) - .withInputType( - if (function.parameters.size == 2) payloadFromType(function.parameters[1].type) - else EMPTY_PAYLOAD) - .withOutputType( - if (function.returnType != null) payloadFromType(function.returnType!!) - else EMPTY_PAYLOAD) - .build()) - } - - private fun defaultHandlerType(componentType: ComponentType, node: KSNode): HandlerType { - when (componentType) { - ComponentType.SERVICE -> return HandlerType.STATELESS - ComponentType.VIRTUAL_OBJECT -> return HandlerType.EXCLUSIVE - ComponentType.WORKFLOW -> - logger.error("Workflow handlers MUST be annotated with either @Shared or @Workflow", node) - } - throw IllegalStateException("Unexpected") - } - - private fun validateMethodSignature( - componentType: ComponentType, - handlerType: HandlerType, - function: KSFunctionDeclaration - ) { - if (function.parameters.isEmpty()) { - logger.error( - "The annotated method has no parameters. There must be at least the context parameter as first parameter", - function) - } - when (handlerType) { - HandlerType.SHARED -> - logger.error( - "The annotation @Shared is not supported by the component type $componentType", - function) - HandlerType.EXCLUSIVE -> - if (componentType == ComponentType.VIRTUAL_OBJECT) { - validateFirstParameterType(ObjectContext::class, function) - } else { - logger.error( - "The annotation @Exclusive is not supported by the component type $componentType", - function) - } - HandlerType.STATELESS -> validateFirstParameterType(Context::class, function) - HandlerType.WORKFLOW -> - logger.error( - "The annotation @Workflow is currently not supported in sdk-api-kotlin", function) - } - } - - private fun validateFirstParameterType(clazz: KClass<*>, function: KSFunctionDeclaration) { - if (function.parameters[0].type.resolve().declaration.qualifiedName!!.asString() != - clazz.qualifiedName) { - logger.error( - "The method signature must have ${clazz.qualifiedName} as first parameter, was ${function.parameters[0].type.resolve().declaration.qualifiedName!!.asString()}", - function) - } - } - - private fun payloadFromType(typeRef: KSTypeReference): PayloadType { - val ty = typeRef.resolve() - return PayloadType(false, typeRef.toString(), boxedType(ty), serdeDecl(ty)) - } - - private fun serdeDecl(ty: KSType): String { - return when (ty) { - builtIns.unitType -> "dev.restate.sdk.kotlin.KtSerdes.UNIT" - else -> "dev.restate.sdk.kotlin.KtSerdes.json<${boxedType(ty)}>()" - } - } - - private fun boxedType(ty: KSType): String { - return when (ty) { - builtIns.unitType -> "Unit" - else -> ty.toString() - } - } -} diff --git a/sdk-api-kotlin-gen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/sdk-api-kotlin-gen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider deleted file mode 100644 index 05d0b596..00000000 --- a/sdk-api-kotlin-gen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider +++ /dev/null @@ -1 +0,0 @@ -dev.restate.sdk.kotlin.gen.ComponentProcessorProvider \ No newline at end of file diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs deleted file mode 100644 index f0d88264..00000000 --- a/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs +++ /dev/null @@ -1,97 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -import dev.restate.sdk.kotlin.Awaitable -import dev.restate.sdk.kotlin.Context -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.Target -import kotlin.time.Duration - -object {{generatedClassSimpleName}} { - - const val COMPONENT_NAME: String = "{{componentName}}" - - {{#handlers}} - private val {{inputSerdeFieldName}}: Serde<{{{boxedInputFqcn}}}> = {{{inputSerdeDecl}}} - private val {{outputSerdeFieldName}}: Serde<{{{boxedOutputFqcn}}}> = {{{outputSerdeDecl}}} - {{/handlers}} - - fun fromContext(ctx: Context{{#isObject}}, key: String{{/isObject}}): ContextClient { - return ContextClient(ctx{{#isObject}}, key{{/isObject}}) - } - - fun fromIngress(ingressClient: dev.restate.sdk.client.IngressClient{{#isObject}}, key: String{{/isObject}}): IngressClient { - return IngressClient(ingressClient{{#isObject}}, key{{/isObject}}); - } - - fun fromIngress(baseUri: String{{#isObject}}, key: String{{/isObject}}): IngressClient { - return IngressClient(dev.restate.sdk.client.IngressClient.defaultClient(baseUri){{#isObject}}, key{{/isObject}}); - } - - class ContextClient(private val ctx: Context{{#isObject}}, private val key: String{{/isObject}}){ - {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}): Awaitable<{{{boxedOutputFqcn}}}> { - return this.ctx.callAsync( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}Unit{{else}}req{{/if}}) - }{{/handlers}} - - fun send(): Send { - return Send() - } - - fun sendDelayed(delay: Duration): SendDelayed { - return SendDelayed(delay) - } - - inner class Send { - {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}) { - this@ContextClient.ctx.send( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this@ContextClient.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}Unit{{else}}req{{/if}}); - }{{/handlers}} - } - - inner class SendDelayed(private val delay: Duration) { - - {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}) { - this@ContextClient.ctx.sendDelayed( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this@ContextClient.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}Unit{{else}}req{{/if}}, - this.delay); - }{{/handlers}} - } - } - - class IngressClient(private val ingressClient: dev.restate.sdk.client.IngressClient{{#isObject}}, private val key: String{{/isObject}}) { - - {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}): {{{boxedOutputFqcn}}} { - return this.ingressClient.call( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/handlers}} - - fun send(): Send { - return Send() - } - - inner class Send { - {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}): String { - return this@IngressClient.ingressClient.send( - {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this@IngressClient.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/handlers}} - } - } -} \ No newline at end of file diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/ComponentAdapter.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/ComponentAdapter.hbs deleted file mode 100644 index d950ac09..00000000 --- a/sdk-api-kotlin-gen/src/main/resources/templates/ComponentAdapter.hbs +++ /dev/null @@ -1,23 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}}{{/if}} - -class {{generatedClassSimpleName}}: dev.restate.sdk.common.ComponentAdapter<{{originalClassFqcn}}> { - - companion object { - const val COMPONENT_NAME: String = "{{componentName}}"; - } - - override fun adapt(bindableComponent: {{originalClassFqcn}}): dev.restate.sdk.common.BindableComponent { - return dev.restate.sdk.kotlin.Component.{{#if isObject}}virtualObject{{else}}service{{/if}}(COMPONENT_NAME) { - {{#handlers}} - handler(dev.restate.sdk.kotlin.Component.HandlerSignature("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}})) { ctx, req -> - {{#if inputEmpty}}bindableComponent.{{name}}(ctx){{else}}bindableComponent.{{name}}(ctx, req){{/if}} - } - {{/handlers}} - } - } - - override fun supportsObject(serviceObject: Any?): Boolean { - return serviceObject is {{originalClassFqcn}}; - } - -} \ No newline at end of file diff --git a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt b/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt deleted file mode 100644 index 34bd3031..00000000 --- a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.annotation.Exclusive -import dev.restate.sdk.annotation.Handler -import dev.restate.sdk.annotation.Service -import dev.restate.sdk.annotation.VirtualObject -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.common.Target -import dev.restate.sdk.core.ProtoUtils.* -import dev.restate.sdk.core.TestDefinitions -import dev.restate.sdk.core.TestDefinitions.TestDefinition -import dev.restate.sdk.core.TestDefinitions.testInvocation -import java.util.stream.Stream - -class CodegenTest : TestDefinitions.TestSuite { - @Service - class ServiceGreeter { - @Handler - suspend fun greet(context: Context, request: String): String { - return request - } - } - - @VirtualObject - class ObjectGreeter { - @Exclusive - suspend fun greet(context: ObjectContext, request: String): String { - return request - } - } - - @VirtualObject - interface GreeterInterface { - @Exclusive suspend fun greet(context: ObjectContext, request: String): String - } - - private class ObjectGreeterImplementedFromInterface : GreeterInterface { - override suspend fun greet(context: ObjectContext, request: String): String { - return request - } - } - - @Service(name = "Empty") - class Empty { - @Handler - suspend fun emptyInput(context: Context): String { - val client = EmptyClient.fromContext(context) - return client.emptyInput().await() - } - - @Handler - suspend fun emptyOutput(context: Context, request: String) { - val client = EmptyClient.fromContext(context) - client.emptyOutput(request).await() - } - - @Handler - suspend fun emptyInputOutput(context: Context) { - val client = EmptyClient.fromContext(context) - client.emptyInputOutput().await() - } - } - - @Service(name = "PrimitiveTypes") - class PrimitiveTypes { - @Handler - suspend fun primitiveOutput(context: Context): Int { - val client = PrimitiveTypesClient.fromContext(context) - return client.primitiveOutput().await() - } - - @Handler - suspend fun primitiveInput(context: Context, input: Int) { - val client = PrimitiveTypesClient.fromContext(context) - client.primitiveInput(input).await() - } - } - - override fun definitions(): Stream { - return Stream.of( - testInvocation({ ServiceGreeter() }, "greet") - .withInput(startMessage(1), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation({ ObjectGreeter() }, "greet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation({ ObjectGreeterImplementedFromInterface() }, "greet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation({ Empty() }, "emptyInput") - .withInput(startMessage(1), inputMessage(), completionMessage(1, "Till")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyInput")), - outputMessage("Till"), - END_MESSAGE) - .named("empty output"), - testInvocation({ Empty() }, "emptyOutput") - .withInput( - startMessage(1), - inputMessage("Francesco"), - completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyOutput"), "Francesco"), - outputMessage(), - END_MESSAGE) - .named("empty output"), - testInvocation({ Empty() }, "emptyInputOutput") - .withInput( - startMessage(1), - inputMessage("Francesco"), - completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyInputOutput")), - outputMessage(), - END_MESSAGE) - .named("empty input and empty output"), - testInvocation({ PrimitiveTypes() }, "primitiveOutput") - .withInput( - startMessage(1), inputMessage(), completionMessage(1, CoreSerdes.JSON_INT, 10)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage( - Target.service("PrimitiveTypes", "primitiveOutput"), CoreSerdes.VOID, null), - outputMessage(CoreSerdes.JSON_INT, 10), - END_MESSAGE) - .named("primitive output"), - testInvocation({ PrimitiveTypes() }, "primitiveInput") - .withInput( - startMessage(1), inputMessage(10), completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage( - Target.service("PrimitiveTypes", "primitiveInput"), CoreSerdes.JSON_INT, 10), - outputMessage(), - END_MESSAGE) - .named("primitive input")) - } -} diff --git a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/KtCodegenTests.kt b/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/KtCodegenTests.kt deleted file mode 100644 index f76303ae..00000000 --- a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/KtCodegenTests.kt +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.core.MockMultiThreaded -import dev.restate.sdk.core.MockSingleThread -import dev.restate.sdk.core.TestDefinitions -import dev.restate.sdk.core.TestDefinitions.TestExecutor -import dev.restate.sdk.core.TestRunner -import java.util.stream.Stream - -class KtCodegenTests : TestRunner() { - override fun executors(): Stream { - return Stream.of(MockSingleThread.INSTANCE, MockMultiThreaded.INSTANCE) - } - - public override fun definitions(): Stream { - return Stream.of(CodegenTest()) - } -} diff --git a/sdk-api-kotlin/build.gradle.kts b/sdk-api-kotlin/build.gradle.kts deleted file mode 100644 index ae710d22..00000000 --- a/sdk-api-kotlin/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - java - kotlin("jvm") - kotlin("plugin.serialization") - `library-publishing-conventions` -} - -description = "Restate SDK Kotlin APIs" - -dependencies { - api(project(":sdk-common")) - - implementation(kotlinLibs.kotlinx.coroutines) - implementation(kotlinLibs.kotlinx.serialization.core) - implementation(kotlinLibs.kotlinx.serialization.json) - - implementation(coreLibs.log4j.api) - - testImplementation(project(":sdk-core")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(coreLibs.log4j.core) - - testImplementation(project(":sdk-core", "testArchive")) -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Awaitables.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Awaitables.kt deleted file mode 100644 index a174afd4..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Awaitables.kt +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.syscalls.Deferred -import dev.restate.sdk.common.syscalls.Result -import dev.restate.sdk.common.syscalls.Syscalls -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.suspendCancellableCoroutine - -internal abstract class BaseAwaitableImpl -internal constructor(internal val syscalls: Syscalls) : Awaitable { - abstract fun deferred(): Deferred<*> - - abstract suspend fun awaitResult(): Result - - override val onAwait: SelectClause - get() = SelectClauseImpl(this) - - override suspend fun await(): T { - val res = awaitResult() - if (!res.isSuccess) { - throw res.failure!! - } - @Suppress("UNCHECKED_CAST") return res.value as T - } -} - -internal class SingleAwaitableImpl( - syscalls: Syscalls, - private val deferred: Deferred -) : BaseAwaitableImpl(syscalls) { - private var result: Result? = null - - override fun deferred(): Deferred<*> { - return deferred - } - - override suspend fun awaitResult(): Result { - if (!deferred().isCompleted) { - suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveDeferred(deferred(), completingUnitContinuation(cont)) - } - } - if (this.result == null) { - this.result = deferred.toResult() - } - return this.result!! - } -} - -internal abstract class BaseSingleMappedAwaitableImpl( - private val inner: BaseAwaitableImpl -) : BaseAwaitableImpl(inner.syscalls) { - private var mappedResult: Result? = null - - override fun deferred(): Deferred<*> { - return inner.deferred() - } - - abstract suspend fun map(res: Result): Result - - override suspend fun awaitResult(): Result { - if (mappedResult == null) { - this.mappedResult = map(inner.awaitResult()) - } - return mappedResult!! - } -} - -internal open class SingleSerdeAwaitableImpl -internal constructor( - syscalls: Syscalls, - deferred: Deferred, - private val serde: Serde, -) : - BaseSingleMappedAwaitableImpl( - SingleAwaitableImpl(syscalls, deferred), - ) { - @Suppress("UNCHECKED_CAST") - override suspend fun map(res: Result): Result { - return if (res.isSuccess) { - // This propagates exceptions as non-terminal - Result.success(serde.deserializeWrappingException(syscalls, res.value!!)) - } else { - res as Result - } - } -} - -internal class UnitAwakeableImpl(syscalls: Syscalls, deferred: Deferred) : - BaseSingleMappedAwaitableImpl(SingleAwaitableImpl(syscalls, deferred)) { - @Suppress("UNCHECKED_CAST") - override suspend fun map(res: Result): Result { - return if (res.isSuccess) { - Result.success(Unit) - } else { - res as Result - } - } -} - -internal class AnyAwaitableImpl -internal constructor(syscalls: Syscalls, private val awaitables: List>) : - BaseSingleMappedAwaitableImpl( - SingleAwaitableImpl( - syscalls, - syscalls.createAnyDeferred( - awaitables.map { (it as BaseAwaitableImpl<*>).deferred() }))), - AnyAwaitable { - - override suspend fun awaitIndex(): Int { - if (!deferred().isCompleted) { - suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveDeferred(deferred(), completingUnitContinuation(cont)) - } - } - - return deferred().toResult()!!.value as Int - } - - @Suppress("UNCHECKED_CAST") - override suspend fun map(res: Result): Result { - return if (res.isSuccess) - ((awaitables[res.value!!] as BaseAwaitableImpl<*>).awaitResult() as Result) - else (res as Result) - } -} - -internal fun wrapAllAwaitable(awaitables: List>): Awaitable { - val syscalls = (awaitables.get(0) as BaseAwaitableImpl<*>).syscalls - return UnitAwakeableImpl( - syscalls, - syscalls.createAllDeferred(awaitables.map { (it as BaseAwaitableImpl<*>).deferred() }), - ) -} - -internal fun wrapAnyAwaitable(awaitables: List>): AnyAwaitable { - val syscalls = (awaitables.get(0) as BaseAwaitableImpl<*>).syscalls - return AnyAwaitableImpl(syscalls, awaitables) -} - -internal class AwakeableImpl -internal constructor( - syscalls: Syscalls, - deferred: Deferred, - serde: Serde, - override val id: String -) : SingleSerdeAwaitableImpl(syscalls, deferred, serde), Awakeable {} - -internal class AwakeableHandleImpl(val syscalls: Syscalls, val id: String) : AwakeableHandle { - override suspend fun resolve(serde: Serde, payload: T) { - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveAwakeable( - id, serde.serializeWrappingException(syscalls, payload), completingUnitContinuation(cont)) - } - } - - override suspend fun reject(reason: String) { - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.rejectAwakeable(id, reason, completingUnitContinuation(cont)) - } - } -} - -internal class SelectClauseImpl(override val awaitable: Awaitable) : SelectClause - -@PublishedApi -internal class SelectImplementation : SelectBuilder { - - private val clauses: MutableList, suspend (Any?) -> R>> = mutableListOf() - - @Suppress("UNCHECKED_CAST") - override fun SelectClause.invoke(block: suspend (T) -> R) { - clauses.add(this.awaitable as Awaitable<*> to block as suspend (Any?) -> R) - } - - suspend fun doSelect(): R { - val index = wrapAnyAwaitable(clauses.map { it.first }).awaitIndex() - val resolved = clauses[index] - return resolved.first.await().let { resolved.second(it) } - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Component.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Component.kt deleted file mode 100644 index 322c4a61..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Component.kt +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.BindableComponent -import dev.restate.sdk.common.ComponentType -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.TerminalException -import dev.restate.sdk.common.syscalls.* -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.apache.logging.log4j.LogManager - -class Component -private constructor(fqsn: String, isKeyed: Boolean, handlers: Map>) : - BindableComponent { - private val componentDefinition = - ComponentDefinition( - fqsn, - ExecutorType.NON_BLOCKING, - if (isKeyed) ComponentType.VIRTUAL_OBJECT else ComponentType.SERVICE, - handlers.values.map { obj: Handler<*, *, *> -> obj.toHandlerDefinition() }) - - override fun definitions() = listOf(this.componentDefinition) - - companion object { - fun service( - name: String, - coroutineContext: CoroutineContext = Dispatchers.Default, - init: ServiceBuilder.() -> Unit - ): Component { - val builder = ServiceBuilder(name, coroutineContext) - builder.init() - return builder.build() - } - - fun virtualObject( - name: String, - coroutineContext: CoroutineContext = Dispatchers.Default, - init: VirtualObjectBuilder.() -> Unit - ): Component { - val builder = VirtualObjectBuilder(name, coroutineContext) - builder.init() - return builder.build() - } - } - - class VirtualObjectBuilder - internal constructor(private val name: String, private val coroutineContext: CoroutineContext) { - private val handlers: MutableMap> = mutableMapOf() - - fun handler( - sig: HandlerSignature, - runner: suspend (ObjectContext, REQ) -> RES - ): VirtualObjectBuilder { - handlers[sig.name] = Handler(sig, runner, coroutineContext) - return this - } - - inline fun handler( - name: String, - noinline runner: suspend (ObjectContext, REQ) -> RES - ) = this.handler(HandlerSignature(name, KtSerdes.json(), KtSerdes.json()), runner) - - fun build() = Component(this.name, true, this.handlers) - } - - class ServiceBuilder - internal constructor(private val name: String, private val coroutineContext: CoroutineContext) { - private val handlers: MutableMap> = mutableMapOf() - - fun handler( - sig: HandlerSignature, - runner: suspend (Context, REQ) -> RES - ): ServiceBuilder { - handlers[sig.name] = Handler(sig, runner, coroutineContext) - return this - } - - inline fun handler( - name: String, - noinline runner: suspend (Context, REQ) -> RES - ) = this.handler(HandlerSignature(name, KtSerdes.json(), KtSerdes.json()), runner) - - fun build() = Component(this.name, false, this.handlers) - } - - class Handler( - private val handlerSignature: HandlerSignature, - private val runner: suspend (CTX, REQ) -> RES, - private val coroutineContext: CoroutineContext - ) : InvocationHandler { - - companion object { - private val LOG = LogManager.getLogger() - } - - fun toHandlerDefinition() = - HandlerDefinition( - handlerSignature.name, - handlerSignature.requestSerde.schema(), - handlerSignature.responseSerde.schema(), - this) - - override fun handle( - syscalls: Syscalls, - input: ByteString, - callback: SyscallCallback - ) { - val ctx: Context = ContextImpl(syscalls) - - val scope = CoroutineScope(coroutineContext) - scope.launch { - val serializedResult: ByteString - - try { - // Parse input - val req: REQ - try { - req = handlerSignature.requestSerde.deserialize(input) - } catch (e: Error) { - throw e - } catch (e: Throwable) { - LOG.warn("Error when deserializing input", e) - throw TerminalException( - TerminalException.Code.INVALID_ARGUMENT, "Cannot deserialize input: " + e.message) - } - - // Execute user code - @Suppress("UNCHECKED_CAST") val res: RES = runner(ctx as CTX, req) - - // Serialize output - try { - serializedResult = handlerSignature.responseSerde.serializeToByteString(res) - } catch (e: Error) { - throw e - } catch (e: Throwable) { - LOG.warn("Error when serializing input", e) - throw TerminalException( - TerminalException.Code.INVALID_ARGUMENT, "Cannot serialize output: $e") - } - } catch (e: Throwable) { - callback.onCancel(e) - return@launch - } - - // Complete callback - callback.onSuccess(serializedResult) - } - } - } - - class HandlerSignature( - val name: String, - val requestSerde: Serde, - val responseSerde: Serde - ) -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt deleted file mode 100644 index 2dd2abbe..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.* -import dev.restate.sdk.common.Target -import dev.restate.sdk.common.syscalls.Deferred -import dev.restate.sdk.common.syscalls.EnterSideEffectSyscallCallback -import dev.restate.sdk.common.syscalls.ExitSideEffectSyscallCallback -import dev.restate.sdk.common.syscalls.Syscalls -import kotlin.coroutines.resume -import kotlin.time.Duration -import kotlin.time.toJavaDuration -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.suspendCancellableCoroutine - -internal class ContextImpl internal constructor(private val syscalls: Syscalls) : ObjectContext { - override fun key(): String { - return this.syscalls.objectKey() - } - - override suspend fun get(key: StateKey): T? { - val deferred: Deferred = - suspendCancellableCoroutine { cont: CancellableContinuation> -> - syscalls.get(key.name(), completingContinuation(cont)) - } - - if (!deferred.isCompleted) { - suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveDeferred(deferred, completingUnitContinuation(cont)) - } - } - - val readyResult = deferred.toResult()!! - if (!readyResult.isSuccess) { - throw readyResult.failure!! - } - if (readyResult.isEmpty) { - return null - } - return key.serde().deserializeWrappingException(syscalls, readyResult.value!!)!! - } - - override suspend fun stateKeys(): Collection { - val deferred: Deferred> = - suspendCancellableCoroutine { cont: CancellableContinuation>> -> - syscalls.getKeys(completingContinuation(cont)) - } - - if (!deferred.isCompleted) { - suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveDeferred(deferred, completingUnitContinuation(cont)) - } - } - - val readyResult = deferred.toResult()!! - if (!readyResult.isSuccess) { - throw readyResult.failure!! - } - return readyResult.value!! - } - - override suspend fun set(key: StateKey, value: T) { - val serializedValue = key.serde().serializeWrappingException(syscalls, value)!! - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.set(key.name(), serializedValue, completingUnitContinuation(cont)) - } - } - - override suspend fun clear(key: StateKey<*>) { - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.clear(key.name(), completingUnitContinuation(cont)) - } - } - - override suspend fun clearAll() { - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.clearAll(completingUnitContinuation(cont)) - } - } - - override fun invocationId(): InvocationId { - return this.syscalls.invocationId() - } - - override suspend fun timer(duration: Duration): Awaitable { - val deferred: Deferred = - suspendCancellableCoroutine { cont: CancellableContinuation> -> - syscalls.sleep(duration.toJavaDuration(), completingContinuation(cont)) - } - - return UnitAwakeableImpl(syscalls, deferred) - } - - override suspend fun callAsync( - target: Target, - inputSerde: Serde, - outputSerde: Serde, - parameter: T - ): Awaitable { - val input = inputSerde.serializeWrappingException(syscalls, parameter) - - val deferred: Deferred = - suspendCancellableCoroutine { cont: CancellableContinuation> -> - syscalls.call(target, input, completingContinuation(cont)) - } - - return SingleSerdeAwaitableImpl(syscalls, deferred, outputSerde) - } - - override suspend fun send(target: Target, inputSerde: Serde, parameter: T) { - val input = inputSerde.serializeWrappingException(syscalls, parameter) - - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.send(target, input, null, completingUnitContinuation(cont)) - } - } - - override suspend fun sendDelayed( - target: Target, - inputSerde: Serde, - parameter: T, - delay: Duration - ) { - val input = inputSerde.serializeWrappingException(syscalls, parameter) - - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.send(target, input, delay.toJavaDuration(), completingUnitContinuation(cont)) - } - } - - override suspend fun sideEffect( - serde: Serde, - sideEffectAction: suspend () -> T - ): T { - val exitResult = - suspendCancellableCoroutine { cont: CancellableContinuation> - -> - syscalls.enterSideEffectBlock( - object : EnterSideEffectSyscallCallback { - override fun onSuccess(t: ByteString?) { - val deferred: CompletableDeferred = CompletableDeferred() - deferred.complete(t!!) - cont.resume(deferred) - } - - override fun onFailure(t: TerminalException) { - val deferred: CompletableDeferred = CompletableDeferred() - deferred.completeExceptionally(t) - cont.resume(deferred) - } - - override fun onCancel(t: Throwable?) { - cont.cancel(t) - } - - override fun onNotExecuted() { - cont.resume(CompletableDeferred()) - } - }) - } - - if (exitResult.isCompleted) { - return serde.deserializeWrappingException(syscalls, exitResult.await())!! - } - - var actionReturnValue: T? = null - var actionFailure: TerminalException? = null - try { - actionReturnValue = sideEffectAction() - } catch (e: TerminalException) { - actionFailure = e - } catch (e: Error) { - throw e - } catch (t: Throwable) { - syscalls.fail(t) - throw CancellationException("Side effect failure", t) - } - - val exitCallback = - object : ExitSideEffectSyscallCallback { - override fun onSuccess(t: ByteString?) { - exitResult.complete(t!!) - } - - override fun onFailure(t: TerminalException) { - exitResult.completeExceptionally(t) - } - - override fun onCancel(t: Throwable?) { - exitResult.cancel(CancellationException("Suspended", t)) - } - } - - if (actionFailure != null) { - syscalls.exitSideEffectBlockWithTerminalException(actionFailure, exitCallback) - } else { - syscalls.exitSideEffectBlock( - serde.serializeWrappingException(syscalls, actionReturnValue), exitCallback) - } - - return serde.deserializeWrappingException(syscalls, exitResult.await()) - } - - override suspend fun awakeable(serde: Serde): Awakeable { - val (aid, deferredResult) = - suspendCancellableCoroutine { - cont: CancellableContinuation>> -> - syscalls.awakeable(completingContinuation(cont)) - } - - return AwakeableImpl(syscalls, deferredResult, serde, aid) - } - - override fun awakeableHandle(id: String): AwakeableHandle { - return AwakeableHandleImpl(syscalls, id) - } - - override fun random(): RestateRandom { - return RestateRandom(syscalls.invocationId().toRandomSeed(), syscalls) - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/KtSerdes.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/KtSerdes.kt deleted file mode 100644 index ddbac707..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/KtSerdes.kt +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.Serde -import java.nio.charset.StandardCharsets -import kotlin.reflect.typeOf -import kotlinx.serialization.KSerializer -import kotlinx.serialization.json.Json -import kotlinx.serialization.serializer - -object KtSerdes { - - /** Creates a [Serde] implementation using the `kotlinx.serialization` json module. */ - inline fun json(): Serde { - @Suppress("UNCHECKED_CAST") - return when (typeOf()) { - typeOf() -> UNIT as Serde - else -> json(serializer()) - } - } - - val UNIT: Serde = - object : Serde { - override fun serialize(value: Unit?): ByteArray { - return ByteArray(0) - } - - override fun serializeToByteString(value: Unit?): ByteString { - return ByteString.EMPTY - } - - override fun deserialize(value: ByteArray) { - return - } - - override fun deserialize(byteString: ByteString) { - return - } - } - - /** Creates a [Serde] implementation using the `kotlinx.serialization` json module. */ - fun json(serializer: KSerializer): Serde { - return object : Serde { - override fun serialize(value: T?): ByteArray { - return Json.encodeToString(serializer, value!!).encodeToByteArray() - } - - override fun deserialize(value: ByteArray?): T { - return Json.decodeFromString(serializer, String(value!!, StandardCharsets.UTF_8)) - } - } - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Util.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Util.kt deleted file mode 100644 index 148e32f8..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Util.kt +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.syscalls.SyscallCallback -import dev.restate.sdk.common.syscalls.Syscalls -import kotlin.coroutines.resume -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CancellationException - -internal fun completingContinuation(cont: CancellableContinuation): SyscallCallback { - return SyscallCallback.of(cont::resume) { - cont.cancel(CancellationException("Restate internal error", it)) - } -} - -internal fun completingUnitContinuation( - cont: CancellableContinuation -): SyscallCallback { - return SyscallCallback.of( - { cont.resume(Unit) }, { cont.cancel(CancellationException("Restate internal error", it)) }) -} - -internal fun Serde.serializeWrappingException( - syscalls: Syscalls, - value: T? -): ByteString? { - return try { - this.serializeToByteString(value) - } catch (e: Exception) { - syscalls.fail(e) - throw CancellationException("Failed serialization", e) - } -} - -internal fun Serde.deserializeWrappingException( - syscalls: Syscalls, - byteString: ByteString -): T { - return try { - this.deserialize(byteString) - } catch (e: Exception) { - syscalls.fail(e) - throw CancellationException("Failed deserialization", e) - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt deleted file mode 100644 index ed39d9b3..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.* -import dev.restate.sdk.common.Target -import dev.restate.sdk.common.syscalls.Syscalls -import java.util.* -import kotlin.random.Random -import kotlin.time.Duration - -/** - * This interface exposes the Restate functionalities to Restate services. It can be used to - * interact with other Restate services, record side effects, execute timers and synchronize with - * external systems. - * - * To use it within your Restate service, implement [RestateKtComponent] and get an instance with - * [RestateKtComponent.restateContext]. - * - * All methods of this interface, and related interfaces, throws either [TerminalException] or - * cancels the coroutine. [TerminalException] can be caught and acted upon. - * - * NOTE: This interface MUST NOT be accessed concurrently since it can lead to different orderings - * of user actions, corrupting the execution of the invocation. - */ -sealed interface Context { - - /** @return this invocation id */ - fun invocationId(): InvocationId - - /** - * Causes the current execution of the function invocation to sleep for the given duration. - * - * @param duration for which to sleep. - */ - suspend fun sleep(duration: Duration) { - timer(duration).await() - } - - /** - * Causes the start of a timer for the given duration. You can await on the timer end by invoking - * [Awaitable.await]. - * - * @param duration for which to sleep. - */ - suspend fun timer(duration: Duration): Awaitable - - /** - * Invoke another Restate service method and wait for the response. Same as - * `call(methodDescriptor, parameter).await()`. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param outputSerde Output serde - * @param parameter the invocation request parameter. - * @return the invocation response. - */ - suspend fun call( - target: Target, - inputSerde: Serde, - outputSerde: Serde, - parameter: T - ): R { - return callAsync(target, inputSerde, outputSerde, parameter).await() - } - - /** - * Invoke another Restate service method. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param outputSerde Output serde - * @param parameter the invocation request parameter. - * @return an [Awaitable] that wraps the Restate service method result. - */ - suspend fun callAsync( - target: Target, - inputSerde: Serde, - outputSerde: Serde, - parameter: T - ): Awaitable - - /** - * Invoke another Restate service without waiting for the response. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param parameter the invocation request parameter. - */ - suspend fun send(target: Target, inputSerde: Serde, parameter: T) - - /** - * Invoke another Restate service without waiting for the response after the provided `delay` has - * elapsed. - * - * This method returns immediately, as the timer is executed and awaited on Restate. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param parameter the invocation request parameter. - * @param delay time to wait before executing the call - */ - suspend fun sendDelayed( - target: Target, - inputSerde: Serde, - parameter: T, - delay: Duration - ) - - /** - * Execute a non-deterministic closure, recording the result value in the journal. The result - * value will be re-played in case of re-invocation (e.g. because of failure recovery or - * suspension point) without re-executing the closure. Use this feature if you want to perform - * non-deterministic operations. - * - *

The closure should tolerate retries, that is Restate might re-execute the closure multiple - * times until it records a result. - * - *

Error handling

- * - * Errors occurring within this closure won't be propagated to the caller, unless they are - * [TerminalException]. Consider the following code: - * ``` - * // Bad usage of try-catch outside the side effect - * try { - * ctx.sideEffect { - * throw IllegalStateException(); - * }; - * } catch (e: IllegalStateException) { - * // This will never be executed, - * // but the error will be retried by Restate, - * // following the invocation retry policy. - * } - * - * // Good usage of try-catch outside the side effect - * try { - * ctx.sideEffect { - * throw TerminalException("my error"); - * }; - * } catch (e: TerminalException) { - * // This is invoked - * } - * ``` - * - * To propagate side effects failures to the side effect call-site, make sure to wrap them in - * [TerminalException]. - * - * @param serde the type tag of the return value, used to serialize/deserialize it. - * @param action to execute for its side effects. - * @param T type of the return value. - * @return value of the side effect operation. - */ - suspend fun sideEffect(serde: Serde, sideEffectAction: suspend () -> T): T - - /** Like [sideEffect] without a return value. */ - suspend fun sideEffect(sideEffectAction: suspend () -> Unit) { - sideEffect(KtSerdes.UNIT, sideEffectAction) - } - - /** - * Create an [Awakeable], addressable through [Awakeable.id]. - * - * You can use this feature to implement external asynchronous systems interactions, for example - * you can send a Kafka record including the [Awakeable.id], and then let another service consume - * from Kafka the responses of given external system interaction by using [awakeableHandle]. - * - * @param serde the response type tag to use for deserializing the [Awakeable] result. - * @return the [Awakeable] to await on. - * @see Awakeable - */ - suspend fun awakeable(serde: Serde): Awakeable - - /** - * Create a new [AwakeableHandle] for the provided identifier. You can use it to - * [AwakeableHandle.resolve] or [AwakeableHandle.reject] the linked [Awakeable]. - * - * @see Awakeable - */ - fun awakeableHandle(id: String): AwakeableHandle - - /** - * Create a [RestateRandom] instance inherently predictable, seeded on the - * [dev.restate.sdk.common.InvocationId], which is not secret. - * - * This instance is useful to generate identifiers, idempotency keys, and for uniform sampling - * from a set of options. If a cryptographically secure value is needed, please generate that - * externally using [sideEffect]. - * - * You MUST NOT use this [Random] instance inside a [sideEffect]. - * - * @return the [Random] instance. - */ - fun random(): RestateRandom -} - -/** - * This interface extends [Context] adding access to the virtual object instance key-value state - * storage. - */ -sealed interface ObjectContext : Context { - - /** @return the key of this object */ - fun key(): String - - /** - * Gets the state stored under key, deserializing the raw value using the [StateKey.serde]. - * - * @param key identifying the state to get and its type. - * @return the value containing the stored state deserialized. - * @throws RuntimeException when the state cannot be deserialized. - */ - suspend fun get(key: StateKey): T? - - /** - * Gets all the known state keys for this virtual object instance. - * - * @return the immutable collection of known state keys. - */ - suspend fun stateKeys(): Collection - - /** - * Sets the given value under the given key, serializing the value using the [StateKey.serde]. - * - * @param key identifying the value to store and its type. - * @param value to store under the given key. - */ - suspend fun set(key: StateKey, value: T) - - /** - * Clears the state stored under key. - * - * @param key identifying the state to clear. - */ - suspend fun clear(key: StateKey<*>) - - /** Clears all the state of this virtual object instance key-value state storage */ - suspend fun clearAll() -} - -class RestateRandom(seed: Long, private val syscalls: Syscalls) : Random() { - private val r = Random(seed) - - override fun nextBits(bitCount: Int): Int { - check(!syscalls.isInsideSideEffect) { "You can't use RestateRandom inside a side effect!" } - return r.nextBits(bitCount) - } - - fun nextUUID(): UUID { - return UUID(this.nextLong(), this.nextLong()) - } -} - -/** - * An [Awaitable] allows to await an asynchronous result. Once [await] is called, the execution - * waits until the asynchronous result is available. - * - * The result can be either a success or a failure. In case of a failure, [await] will throw a - * [dev.restate.sdk.core.TerminalException]. - * - * @param T type of the awaitable result - */ -sealed interface Awaitable { - suspend fun await(): T - - /** Clause for [select] operator. */ - val onAwait: SelectClause - - companion object { - fun all( - first: Awaitable<*>, - second: Awaitable<*>, - vararg others: Awaitable<*> - ): Awaitable { - return wrapAllAwaitable(listOf(first) + listOf(second) + others.asList()) - } - - fun any(first: Awaitable<*>, second: Awaitable<*>, vararg others: Awaitable<*>): AnyAwaitable { - return wrapAnyAwaitable(listOf(first) + listOf(second) + others.asList()) - } - } -} - -/** Like [kotlinx.coroutines.awaitAll], but for [Awaitable]. */ -suspend fun Collection>.awaitAll(): List { - return awaitAll(*toTypedArray()) -} - -/** - * Like [kotlinx.coroutines.awaitAll], but for [Awaitable]. - * - * ``` - * val ctx = restateContext() - * val a1 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Francesco" }) - * val a2 = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Till" }) - * - * val result = listOf(a1, a2) - * .awaitAll() - * .joinToString(separator = "-", transform = GreetingResponse::getMessage) - * ``` - */ -suspend fun awaitAll(vararg awaitables: Awaitable): List { - if (awaitables.isEmpty()) { - return emptyList() - } - if (awaitables.size == 1) { - return listOf(awaitables[0].await()) - } - wrapAllAwaitable(awaitables.asList()).await() - return awaitables.map { it.await() }.toList() -} - -sealed interface AnyAwaitable : Awaitable { - /** Same as [Awaitable.await], but returns the index of the first completed element. */ - suspend fun awaitIndex(): Int -} - -/** - * Like [kotlinx.coroutines.selects.select], but for [Awaitable] - * - * ``` - * val ctx = restateContext() - * val callAwaitable = ctx.callAsync(GreeterGrpcKt.greetMethod, greetingRequest { name = "Francesco" }) - * val timeout = ctx.timer(10.seconds) - * val result = select { - * callAwaitable.onAwait { it.message } - * timeout.onAwait { throw TimeoutException() } - * } - * ``` - */ -suspend inline fun select(crossinline builder: SelectBuilder.() -> Unit): R { - val selectImpl = SelectImplementation() - builder.invoke(selectImpl) - return selectImpl.doSelect() -} - -sealed interface SelectBuilder { - /** Registers a clause in this [select] expression. */ - operator fun SelectClause.invoke(block: suspend (T) -> R) -} - -sealed interface SelectClause { - val awaitable: Awaitable -} - -/** - * An [Awakeable] is a special type of [Awaitable] which can be arbitrarily completed by another - * service, by addressing it with its [id]. - * - * It can be used to let a service wait on a specific condition/result, which is fulfilled by - * another service or by an external system at a later point in time. - * - * For example, you can send a Kafka record including the [Awakeable.id], and then let another - * service consume from Kafka the responses of given external system interaction by using - * [RestateContext.awakeableHandle]. - */ -sealed interface Awakeable : Awaitable { - /** The unique identifier of this [Awakeable] instance. */ - val id: String -} - -/** This class represents a handle to an [Awakeable] created in another service. */ -sealed interface AwakeableHandle { - /** - * Complete with success the [Awakeable]. - * - * @param serde used to serialize the [Awakeable] result payload. - * @param payload the result payload. - * @see Awakeable - */ - suspend fun resolve(serde: Serde, payload: T) - - /** - * Complete with failure the [Awakeable]. - * - * @param reason the rejection reason. - * @see Awakeable - */ - suspend fun reject(reason: String) -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwakeableIdTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwakeableIdTest.kt deleted file mode 100644 index 1adcea7c..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwakeableIdTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.core.AwakeableIdTestSuite -import dev.restate.sdk.core.TestDefinitions -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForService - -class AwakeableIdTest : AwakeableIdTestSuite() { - - override fun returnAwakeableId(): TestDefinitions.TestInvocationBuilder = - testDefinitionForService("ReturnAwakeableId") { ctx, _: Unit -> - val awakeable = ctx.awakeable(CoreSerdes.JSON_STRING) - awakeable.id - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/DeferredTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/DeferredTest.kt deleted file mode 100644 index 0aac0b45..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/DeferredTest.kt +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.core.DeferredTestSuite -import dev.restate.sdk.core.TestDefinitions.* -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.callGreeterGreetService -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForVirtualObject -import java.util.stream.Stream - -class DeferredTest : DeferredTestSuite() { - override fun reverseAwaitOrder(): TestInvocationBuilder = - testDefinitionForVirtualObject("ReverseAwaitOrder") { ctx, _: Unit -> - val a1: Awaitable = callGreeterGreetService(ctx, "Francesco") - val a2: Awaitable = callGreeterGreetService(ctx, "Till") - - val a2Res: String = a2.await() - ctx.set(StateKey.of("A2", CoreSerdes.JSON_STRING), a2Res) - - val a1Res: String = a1.await() - "$a1Res-$a2Res" - } - - override fun awaitTwiceTheSameAwaitable(): TestInvocationBuilder = - testDefinitionForVirtualObject("AwaitTwiceTheSameAwaitable") { ctx, _: Unit -> - val a = callGreeterGreetService(ctx, "Francesco") - "${a.await()}-${a.await()}" - } - - override fun awaitAll(): TestInvocationBuilder = - testDefinitionForVirtualObject("AwaitAll") { ctx, _: Unit -> - val a1 = callGreeterGreetService(ctx, "Francesco") - val a2 = callGreeterGreetService(ctx, "Till") - - listOf(a1, a2).awaitAll().joinToString(separator = "-") - } - - override fun awaitAny(): TestInvocationBuilder = - testDefinitionForVirtualObject("AwaitAny") { ctx, _: Unit -> - val a1 = callGreeterGreetService(ctx, "Francesco") - val a2 = callGreeterGreetService(ctx, "Till") - Awaitable.any(a1, a2).await() as String - } - - private fun awaitSelect(): TestInvocationBuilder = - testDefinitionForVirtualObject("AwaitSelect") { ctx, _: Unit -> - val a1 = callGreeterGreetService(ctx, "Francesco") - val a2 = callGreeterGreetService(ctx, "Till") - select { - a1.onAwait { it } - a2.onAwait { it } - } - } - - override fun combineAnyWithAll(): TestInvocationBuilder = - testDefinitionForVirtualObject("CombineAnyWithAll") { ctx, _: Unit -> - val a1 = ctx.awakeable(CoreSerdes.JSON_STRING) - val a2 = ctx.awakeable(CoreSerdes.JSON_STRING) - val a3 = ctx.awakeable(CoreSerdes.JSON_STRING) - val a4 = ctx.awakeable(CoreSerdes.JSON_STRING) - - val a12 = Awaitable.any(a1, a2) - val a23 = Awaitable.any(a2, a3) - val a34 = Awaitable.any(a3, a4) - Awaitable.all(a12, a23, a34).await() - - a12.await().toString() + a23.await() as String + a34.await() - } - - override fun awaitAnyIndex(): TestInvocationBuilder = - testDefinitionForVirtualObject("AwaitAnyIndex") { ctx, _: Unit -> - val a1 = ctx.awakeable(CoreSerdes.JSON_STRING) - val a2 = ctx.awakeable(CoreSerdes.JSON_STRING) - val a3 = ctx.awakeable(CoreSerdes.JSON_STRING) - val a4 = ctx.awakeable(CoreSerdes.JSON_STRING) - - Awaitable.any(a1, Awaitable.all(a2, a3), a4).awaitIndex().toString() - } - - override fun awaitOnAlreadyResolvedAwaitables(): TestInvocationBuilder = - testDefinitionForVirtualObject("AwaitOnAlreadyResolvedAwaitables") { ctx, _: Unit -> - val a1 = ctx.awakeable(CoreSerdes.JSON_STRING) - val a2 = ctx.awakeable(CoreSerdes.JSON_STRING) - val a12 = Awaitable.all(a1, a2) - val a12and1 = Awaitable.all(a12, a1) - val a121and12 = Awaitable.all(a12and1, a12) - a12and1.await() - a121and12.await() - - a1.await() + a2.await() - } - - override fun awaitWithTimeout(): TestInvocationBuilder { - return unsupported("This is a feature not available in sdk-api-kotlin") - } - - override fun definitions(): Stream = - Stream.concat(super.definitions(), super.anyTestDefinitions { awaitSelect() }) -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/EagerStateTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/EagerStateTest.kt deleted file mode 100644 index b6f50f68..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/EagerStateTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.core.EagerStateTestSuite -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForVirtualObject -import org.assertj.core.api.AssertionsForClassTypes.assertThat - -class EagerStateTest : EagerStateTestSuite() { - override fun getEmpty(): TestInvocationBuilder = - testDefinitionForVirtualObject("GetEmpty") { ctx, _: Unit -> - val stateIsEmpty = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)) == null - stateIsEmpty.toString() - } - - override fun get(): TestInvocationBuilder = - testDefinitionForVirtualObject("GetEmpty") { ctx, _: Unit -> - ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! - } - - override fun getAppendAndGet(): TestInvocationBuilder = - testDefinitionForVirtualObject("GetAppendAndGet") { ctx, name: String -> - val oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! - ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), oldState + name) - ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! - } - - override fun getClearAndGet(): TestInvocationBuilder = - testDefinitionForVirtualObject("GetClearAndGet") { ctx, _: Unit -> - val oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! - ctx.clear(StateKey.of("STATE", CoreSerdes.JSON_STRING)) - assertThat(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))).isNull() - oldState - } - - override fun getClearAllAndGet(): TestInvocationBuilder = - testDefinitionForVirtualObject("GetClearAllAndGet") { ctx, _: Unit -> - val oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! - - ctx.clearAll() - - assertThat(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))).isNull() - assertThat(ctx.get(StateKey.of("ANOTHER_STATE", CoreSerdes.JSON_STRING))).isNull() - oldState - } - - override fun listKeys(): TestInvocationBuilder = - testDefinitionForVirtualObject("ListKeys") { ctx, _: Unit -> - ctx.stateKeys().joinToString(separator = ",") - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/InvocationIdTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/InvocationIdTest.kt deleted file mode 100644 index 3df85221..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/InvocationIdTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.core.InvocationIdTestSuite -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForService - -class InvocationIdTest : InvocationIdTestSuite() { - - override fun returnInvocationId(): TestInvocationBuilder = - testDefinitionForService("ReturnInvocationId") { ctx, _: Unit -> - ctx.invocationId().toString() - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/KotlinCoroutinesTests.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/KotlinCoroutinesTests.kt deleted file mode 100644 index 6d63b566..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/KotlinCoroutinesTests.kt +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.core.* -import dev.restate.sdk.core.TestDefinitions.TestExecutor -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder -import java.util.stream.Stream -import kotlinx.coroutines.Dispatchers - -class KotlinCoroutinesTests : TestRunner() { - override fun executors(): Stream { - return Stream.of(MockSingleThread.INSTANCE, MockMultiThreaded.INSTANCE) - } - - public override fun definitions(): Stream { - return Stream.of( - AwakeableIdTest(), - DeferredTest(), - EagerStateTest(), - StateTest(), - InvocationIdTest(), - OnlyInputAndOutputTest(), - SideEffectTest(), - SleepTest(), - StateMachineFailuresTest(), - UserFailuresTest(), - RandomTest()) - } - - companion object { - inline fun testDefinitionForService( - name: String, - noinline runner: suspend (Context, REQ) -> RES - ): TestInvocationBuilder { - return TestDefinitions.testInvocation( - Component.service(name, Dispatchers.Unconfined) { handler("run", runner) }, "run") - } - - inline fun testDefinitionForVirtualObject( - name: String, - noinline runner: suspend (ObjectContext, REQ) -> RES - ): TestInvocationBuilder { - return TestDefinitions.testInvocation( - Component.virtualObject(name, Dispatchers.Unconfined) { handler("run", runner) }, "run") - } - - suspend fun callGreeterGreetService(ctx: Context, parameter: String): Awaitable { - return ctx.callAsync( - ProtoUtils.GREETER_SERVICE_TARGET, - CoreSerdes.JSON_STRING, - CoreSerdes.JSON_STRING, - parameter) - } - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/OnlyInputAndOutputTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/OnlyInputAndOutputTest.kt deleted file mode 100644 index 321c3690..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/OnlyInputAndOutputTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.core.OnlyInputAndOutputTestSuite -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForService - -class OnlyInputAndOutputTest : OnlyInputAndOutputTestSuite() { - - override fun noSyscallsGreeter(): TestInvocationBuilder = - testDefinitionForService("NoSyscallsGreeter") { _, name: String -> "Hello $name" } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RandomTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RandomTest.kt deleted file mode 100644 index a11b6329..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RandomTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.core.RandomTestSuite -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForService -import kotlin.random.Random - -class RandomTest : RandomTestSuite() { - override fun randomShouldBeDeterministic(): TestInvocationBuilder = - testDefinitionForService("RandomShouldBeDeterministic") { ctx, _: Unit -> - ctx.random().nextInt() - } - - override fun randomInsideSideEffect(): TestInvocationBuilder = - testDefinitionForService("RandomInsideSideEffect") { ctx, _: Unit -> - ctx.sideEffect { ctx.random().nextInt() } - throw IllegalStateException("This should not unreachable") - } - - override fun getExpectedInt(seed: Long): Int { - return Random(seed).nextInt() - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SideEffectTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SideEffectTest.kt deleted file mode 100644 index 5fe2de37..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SideEffectTest.kt +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.core.ProtoUtils.GREETER_SERVICE_TARGET -import dev.restate.sdk.core.SideEffectTestSuite -import dev.restate.sdk.core.TestDefinitions -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForService -import java.util.* -import kotlin.coroutines.coroutineContext -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.Dispatchers - -class SideEffectTest : SideEffectTestSuite() { - - override fun sideEffect(sideEffectOutput: String): TestInvocationBuilder = - testDefinitionForService("SideEffect") { ctx, _: Unit -> - val result = ctx.sideEffect(CoreSerdes.JSON_STRING) { sideEffectOutput } - "Hello $result" - } - - override fun consecutiveSideEffect(sideEffectOutput: String): TestInvocationBuilder = - testDefinitionForService("ConsecutiveSideEffect") { ctx, _: Unit -> - val firstResult = ctx.sideEffect(CoreSerdes.JSON_STRING) { sideEffectOutput } - val secondResult = - ctx.sideEffect(CoreSerdes.JSON_STRING) { firstResult.uppercase(Locale.getDefault()) } - "Hello $secondResult" - } - - override fun checkContextSwitching(): TestInvocationBuilder = - TestDefinitions.testInvocation( - Component.service( - "CheckContextSwitching", - Dispatchers.Unconfined + CoroutineName("CheckContextSwitchingTestCoroutine")) { - handler("run") { ctx, _: Unit -> - val sideEffectCoroutine = - ctx.sideEffect(CoreSerdes.JSON_STRING) { - coroutineContext[CoroutineName.Key]!!.name - } - check(sideEffectCoroutine == "CheckContextSwitchingTestCoroutine") { - "Side effect thread is not running within the same coroutine context of the handler method: $sideEffectCoroutine" - } - "Hello" - } - }, - "run") - - override fun sideEffectGuard(): TestInvocationBuilder = - testDefinitionForService("SideEffectGuard") { ctx, _: Unit -> - ctx.sideEffect { ctx.send(GREETER_SERVICE_TARGET, KtSerdes.json(), "something") } - throw IllegalStateException("This point should not be reached") - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SleepTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SleepTest.kt deleted file mode 100644 index 4881b1b5..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SleepTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.core.SleepTestSuite -import dev.restate.sdk.core.TestDefinitions -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForService -import kotlin.time.Duration.Companion.milliseconds - -class SleepTest : SleepTestSuite() { - - override fun sleepGreeter(): TestDefinitions.TestInvocationBuilder = - testDefinitionForService("SleepGreeter") { ctx, _: Unit -> - ctx.sleep(1000.milliseconds) - "Hello" - } - - override fun manySleeps(): TestDefinitions.TestInvocationBuilder = - testDefinitionForService("ManySleeps") { ctx, _: Unit -> - val awaitables = mutableListOf>() - for (i in 0..9) { - awaitables.add(ctx.timer(1000.milliseconds)) - } - awaitables.awaitAll() - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateMachineFailuresTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateMachineFailuresTest.kt deleted file mode 100644 index fac7654d..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateMachineFailuresTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.common.TerminalException -import dev.restate.sdk.core.StateMachineFailuresTestSuite -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForService -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForVirtualObject -import java.nio.charset.StandardCharsets -import java.util.concurrent.atomic.AtomicInteger -import kotlinx.coroutines.CancellationException - -class StateMachineFailuresTest : StateMachineFailuresTestSuite() { - companion object { - private val STATE = - StateKey.of( - "STATE", - Serde.using({ i: Int -> i.toString().toByteArray(StandardCharsets.UTF_8) }) { - b: ByteArray? -> - String(b!!, StandardCharsets.UTF_8).toInt() - }) - } - - override fun getState(nonTerminalExceptionsSeen: AtomicInteger): TestInvocationBuilder = - testDefinitionForVirtualObject("GetState") { ctx, _: Unit -> - try { - ctx.get(STATE) - } catch (e: Throwable) { - // A user should never catch Throwable!!! - if (e !is CancellationException && e !is TerminalException) { - nonTerminalExceptionsSeen.addAndGet(1) - } else { - throw e - } - } - "Francesco" - } - - override fun sideEffectFailure(serde: Serde): TestInvocationBuilder = - testDefinitionForService("SideEffectFailure") { ctx, _: Unit -> - ctx.sideEffect(serde) { 0 } - "Francesco" - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateTest.kt deleted file mode 100644 index 24fa558a..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.core.ProtoUtils.* -import dev.restate.sdk.core.StateTestSuite -import dev.restate.sdk.core.TestDefinitions.* -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForVirtualObject -import java.util.stream.Stream -import kotlinx.serialization.Serializable - -class StateTest : StateTestSuite() { - - override fun getState(): TestInvocationBuilder = - testDefinitionForVirtualObject("GetState") { ctx, _: Unit -> - val state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)) ?: "Unknown" - "Hello $state" - } - - override fun getAndSetState(): TestInvocationBuilder = - testDefinitionForVirtualObject("GetAndSetState") { ctx, name: String -> - val state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))!! - ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), name) - "Hello $state" - } - - override fun setNullState(): TestInvocationBuilder { - return unsupported("The kotlin type system enforces non null state values") - } - - // --- Test using KTSerdes - - @Serializable data class Data(var a: Int, val b: String) - - private companion object { - val DATA: StateKey = StateKey.of("STATE", KtSerdes.json()) - } - - private fun getAndSetStateUsingKtSerdes(): TestInvocationBuilder = - testDefinitionForVirtualObject("GetAndSetStateUsingKtSerdes") { ctx, _: Unit -> - val state = ctx.get(DATA)!! - state.a += 1 - ctx.set(DATA, state) - - "Hello $state" - } - - override fun definitions(): Stream { - return Stream.concat( - super.definitions(), - Stream.of( - getAndSetStateUsingKtSerdes() - .withInput( - startMessage(3), - inputMessage(), - getStateMessage("STATE", KtSerdes.json(), Data(1, "Till")), - setStateMessage("STATE", KtSerdes.json(), Data(2, "Till"))) - .expectingOutput(outputMessage("Hello " + Data(2, "Till")), END_MESSAGE) - .named("With GetState and SetState"), - getAndSetStateUsingKtSerdes() - .withInput( - startMessage(2), - inputMessage(), - getStateMessage("STATE", KtSerdes.json(), Data(1, "Till"))) - .expectingOutput( - setStateMessage("STATE", KtSerdes.json(), Data(2, "Till")), - outputMessage("Hello " + Data(2, "Till")), - END_MESSAGE) - .named("With GetState already completed"), - )) - } -} diff --git a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/UserFailuresTest.kt b/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/UserFailuresTest.kt deleted file mode 100644 index d655f136..00000000 --- a/sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/UserFailuresTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.TerminalException -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder -import dev.restate.sdk.core.UserFailuresTestSuite -import dev.restate.sdk.kotlin.KotlinCoroutinesTests.Companion.testDefinitionForService -import java.util.concurrent.atomic.AtomicInteger -import kotlin.coroutines.cancellation.CancellationException - -class UserFailuresTest : UserFailuresTestSuite() { - override fun throwIllegalStateException(): TestInvocationBuilder = - testDefinitionForService("ThrowIllegalStateException") { _, _: Unit -> - throw IllegalStateException("Whatever") - } - - override fun sideEffectThrowIllegalStateException( - nonTerminalExceptionsSeen: AtomicInteger - ): TestInvocationBuilder = - testDefinitionForService("SideEffectThrowIllegalStateException") { ctx, _: Unit -> - try { - ctx.sideEffect { throw IllegalStateException("Whatever") } - } catch (e: Throwable) { - if (e !is CancellationException && e !is TerminalException) { - nonTerminalExceptionsSeen.addAndGet(1) - } else { - throw e - } - } - throw IllegalStateException("Not expected to reach this point") - } - - override fun throwTerminalException( - code: TerminalException.Code, - message: String - ): TestInvocationBuilder = - testDefinitionForService("ThrowTerminalException") { _, _: Unit -> - throw TerminalException(code, message) - } - - override fun sideEffectThrowTerminalException( - code: TerminalException.Code, - message: String - ): TestInvocationBuilder = - testDefinitionForService("SideEffectThrowTerminalException") { ctx, _: Unit -> - ctx.sideEffect { throw TerminalException(code, message) } - throw IllegalStateException("Not expected to reach this point") - } -} diff --git a/sdk-api/build.gradle.kts b/sdk-api/build.gradle.kts deleted file mode 100644 index ab35a016..00000000 --- a/sdk-api/build.gradle.kts +++ /dev/null @@ -1,33 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` -} - -description = "Restate SDK APIs" - -dependencies { - compileOnly(coreLibs.jspecify) - - api(project(":sdk-common")) - - testImplementation(project(":sdk-core")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(coreLibs.protobuf.java) - testImplementation(coreLibs.log4j.core) - - // Import test suites from sdk-core - testImplementation(project(":sdk-core", "testArchive")) -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-api/src/main/java/dev/restate/sdk/AnyAwaitable.java b/sdk-api/src/main/java/dev/restate/sdk/AnyAwaitable.java deleted file mode 100644 index 654a699c..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/AnyAwaitable.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.Result; -import dev.restate.sdk.common.syscalls.Syscalls; -import java.util.List; - -public final class AnyAwaitable extends Awaitable.MappedAwaitable { - - @SuppressWarnings({"unchecked", "rawtypes"}) - AnyAwaitable(Syscalls syscalls, Deferred deferred, List> nested) { - super( - new SingleAwaitable<>(syscalls, deferred), - res -> - res.isSuccess() - ? (Result) nested.get(res.getValue()).awaitResult() - : (Result) res); - } - - /** Same as {@link #await()}, but returns the index. */ - public int awaitIndex() { - // This cast is safe b/c of the constructor - return (int) Util.blockOnResolve(this.syscalls, this.deferred()); - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/Awaitable.java b/sdk-api/src/main/java/dev/restate/sdk/Awaitable.java deleted file mode 100644 index 4941cf3a..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/Awaitable.java +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.AbortedExecutionException; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.common.function.ThrowingFunction; -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.Result; -import dev.restate.sdk.common.syscalls.Syscalls; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * An {@code Awaitable} allows to await an asynchronous result. Once {@code await()} is called, the - * execution stops until the asynchronous result is available. - * - *

The result can be either a success or a failure. In case of a failure, {@code await()} will - * throw a {@link TerminalException}. - * - *

NOTE: This interface MUST NOT be accessed concurrently since it can lead to different - * orderings of user actions, corrupting the execution of the invocation. - * - * @param type of the awaitable result - */ -public abstract class Awaitable { - - protected final Syscalls syscalls; - - Awaitable(Syscalls syscalls) { - this.syscalls = syscalls; - } - - protected abstract Deferred deferred(); - - protected abstract Result awaitResult(); - - /** - * Wait for the current awaitable to complete. Executing this method may trigger the suspension of - * the function. - * - *

NOTE: You should never wrap this invocation in a try-catch catching {@link - * RuntimeException}, as it will catch {@link AbortedExecutionException} as well. - * - * @throws TerminalException if the awaitable is ready and contains a failure - */ - public final T await() throws TerminalException { - return Util.unwrapResult(this.awaitResult()); - } - - /** - * Same as {@link #await()}, but throws a {@link TimeoutException} if this {@link Awaitable} - * doesn't complete before the provided {@code timeout}. - */ - public final T await(Duration timeout) throws TerminalException, TimeoutException { - Deferred sleep = Util.blockOnSyscall(cb -> this.syscalls.sleep(timeout, cb)); - Awaitable sleepAwaitable = single(this.syscalls, sleep); - - int index = any(this, sleepAwaitable).awaitIndex(); - - if (index == 1) { - throw new TimeoutException(); - } - // This await is no-op now - return this.await(); - } - - /** Map the result of this {@link Awaitable}. */ - public final Awaitable map(ThrowingFunction mapper) { - return new MappedAwaitable<>( - this, - result -> { - if (result.isSuccess()) { - return Result.success( - Util.executeMappingException(this.syscalls, mapper, result.getValue())); - } - //noinspection unchecked - return (Result) result; - }); - } - - static Awaitable single(Syscalls syscalls, Deferred deferred) { - return new SingleAwaitable<>(syscalls, deferred); - } - - /** - * Create an {@link Awaitable} that awaits any of the given awaitables. - * - *

The behavior is the same as {@link - * java.util.concurrent.CompletableFuture#anyOf(CompletableFuture[])}. - */ - public static AnyAwaitable any(Awaitable first, Awaitable second, Awaitable... others) { - List> awaitables = new ArrayList<>(2 + others.length); - awaitables.add(first); - awaitables.add(second); - awaitables.addAll(Arrays.asList(others)); - - return new AnyAwaitable( - first.syscalls, - first.syscalls.createAnyDeferred( - awaitables.stream().map(Awaitable::deferred).collect(Collectors.toList())), - awaitables); - } - - /** - * Create an {@link Awaitable} that awaits all the given awaitables. - * - *

The behavior is the same as {@link - * java.util.concurrent.CompletableFuture#allOf(CompletableFuture[])}. - */ - public static Awaitable all( - Awaitable first, Awaitable second, Awaitable... others) { - List> deferred = new ArrayList<>(2 + others.length); - deferred.add(first.deferred()); - deferred.add(second.deferred()); - Arrays.stream(others).map(Awaitable::deferred).forEach(deferred::add); - - return single(first.syscalls, first.syscalls.createAllDeferred(deferred)); - } - - static class SingleAwaitable extends Awaitable { - - private final Deferred deferred; - private Result result; - - SingleAwaitable(Syscalls syscalls, Deferred deferred) { - super(syscalls); - this.deferred = deferred; - } - - @Override - protected Deferred deferred() { - return this.deferred; - } - - @Override - protected Result awaitResult() { - if (!this.deferred.isCompleted()) { - Util.blockOnSyscall(cb -> syscalls.resolveDeferred(this.deferred, cb)); - } - if (this.result == null) { - this.result = this.deferred.toResult(); - } - return this.result; - } - } - - static class MappedAwaitable extends Awaitable { - - private final Awaitable inner; - private final Function, Result> mapper; - private Result mappedResult; - - MappedAwaitable(Awaitable inner, Function, Result> mapper) { - super(inner.syscalls); - this.inner = inner; - this.mapper = mapper; - } - - @Override - protected Deferred deferred() { - return inner.deferred(); - } - - @Override - public Result awaitResult() throws TerminalException { - if (mappedResult == null) { - this.mappedResult = this.mapper.apply(this.inner.awaitResult()); - } - return this.mappedResult; - } - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/Awakeable.java b/sdk-api/src/main/java/dev/restate/sdk/Awakeable.java deleted file mode 100644 index b36bf5e2..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/Awakeable.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.Result; -import dev.restate.sdk.common.syscalls.Syscalls; - -/** - * An {@link Awakeable} is a special type of {@link Awaitable} which can be arbitrarily completed by - * another service, by addressing it with its {@link #id()}. - * - *

It can be used to let a service wait on a specific condition/result, which is fulfilled by - * another service or by an external system at a later point in time. - * - *

For example, you can send a Kafka record including the {@link Awakeable#id()}, and then let - * another service consume from Kafka the responses of given external system interaction by using - * {@link ObjectContext#awakeableHandle(String)}. - * - *

NOTE: This interface MUST NOT be accessed concurrently since it can lead to different - * orderings of user actions, corrupting the execution of the invocation. - */ -public final class Awakeable extends Awaitable.MappedAwaitable { - - private final String identifier; - - Awakeable(Syscalls syscalls, Deferred deferred, Serde serde, String identifier) { - super( - Awaitable.single(syscalls, deferred), - res -> { - if (res.isSuccess()) { - return Result.success( - Util.deserializeWrappingException(syscalls, serde, res.getValue())); - } - //noinspection unchecked - return (Result) res; - }); - this.identifier = identifier; - } - - /** - * @return the unique identifier of this {@link Awakeable} instance. - */ - public String id() { - return identifier; - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/AwakeableHandle.java b/sdk-api/src/main/java/dev/restate/sdk/AwakeableHandle.java deleted file mode 100644 index d335564b..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/AwakeableHandle.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.Serde; -import org.jspecify.annotations.NonNull; - -/** This class represents a handle to an {@link Awakeable} created in another service. */ -public interface AwakeableHandle { - - /** - * Complete with success the {@link Awakeable}. - * - * @param serde used to serialize the {@link Awakeable} result payload. - * @param payload the result payload. MUST NOT be null. - * @see Awakeable - */ - void resolve(Serde serde, @NonNull T payload); - - /** - * Complete with failure the {@link Awakeable}. - * - * @param reason the rejection reason. MUST NOT be null. - * @see Awakeable - */ - void reject(String reason); -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/Component.java b/sdk-api/src/main/java/dev/restate/sdk/Component.java deleted file mode 100644 index 6a2e00e6..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/Component.java +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.ComponentType; -import dev.restate.sdk.common.syscalls.*; -import java.util.*; -import java.util.function.BiFunction; -import java.util.stream.Collectors; - -public final class Component implements BindableComponent { - private final ComponentDefinition componentDefinition; - - private Component(String fqsn, boolean isKeyed, HashMap> handlers) { - this.componentDefinition = - new ComponentDefinition( - fqsn, - ExecutorType.BLOCKING, - isKeyed ? ComponentType.VIRTUAL_OBJECT : ComponentType.SERVICE, - handlers.values().stream() - .map(Handler::toHandlerDefinition) - .collect(Collectors.toList())); - } - - public static ServiceBuilder service(String name) { - return new ServiceBuilder(name); - } - - public static VirtualObjectBuilder virtualObject(String name) { - return new VirtualObjectBuilder(name); - } - - @Override - public List definitions() { - return List.of(this.componentDefinition); - } - - public static class VirtualObjectBuilder { - private final String name; - private final HashMap> handlers; - - VirtualObjectBuilder(String name) { - this.name = name; - this.handlers = new HashMap<>(); - } - - public VirtualObjectBuilder with( - HandlerSignature sig, BiFunction runner) { - this.handlers.put(sig.getName(), new Handler<>(sig, runner)); - return this; - } - - public Component build() { - return new Component(this.name, true, this.handlers); - } - } - - public static class ServiceBuilder { - private final String name; - private final HashMap> methods; - - ServiceBuilder(String name) { - this.name = name; - this.methods = new HashMap<>(); - } - - public ServiceBuilder with( - HandlerSignature sig, BiFunction runner) { - this.methods.put(sig.getName(), new Handler<>(sig, runner)); - return this; - } - - public Component build() { - return new Component(this.name, false, this.methods); - } - } - - @SuppressWarnings("unchecked") - public static class Handler implements InvocationHandler { - private final HandlerSignature handlerSignature; - - private final BiFunction runner; - - public Handler( - HandlerSignature handlerSignature, - BiFunction runner) { - this.handlerSignature = handlerSignature; - this.runner = (BiFunction) runner; - } - - public HandlerSignature getHandlerSignature() { - return handlerSignature; - } - - public BiFunction getRunner() { - return runner; - } - - public HandlerDefinition toHandlerDefinition() { - return new HandlerDefinition( - this.handlerSignature.name, - this.handlerSignature.requestSerde.schema(), - this.handlerSignature.responseSerde.schema(), - this); - } - - @Override - public void handle(Syscalls syscalls, ByteString input, SyscallCallback callback) { - // Any context switching, if necessary, will be done by ResolvedEndpointHandler - Context ctx = new ContextImpl(syscalls); - - // Parse input - REQ req; - try { - req = this.handlerSignature.requestSerde.deserialize(input); - } catch (Error e) { - throw e; - } catch (Throwable e) { - throw new TerminalException( - TerminalException.Code.INVALID_ARGUMENT, "Cannot deserialize input: " + e.getMessage()); - } - - // Execute user code - RES res = this.runner.apply(ctx, req); - - // Serialize output - ByteString serializedResult; - try { - serializedResult = this.handlerSignature.responseSerde.serializeToByteString(res); - } catch (Error e) { - throw e; - } catch (Throwable e) { - throw new TerminalException( - TerminalException.Code.INVALID_ARGUMENT, "Cannot serialize output: " + e.getMessage()); - } - - // Complete callback - callback.onSuccess(serializedResult); - } - } - - public static class HandlerSignature { - - private final String name; - private final Serde requestSerde; - private final Serde responseSerde; - - HandlerSignature(String name, Serde requestSerde, Serde responseSerde) { - this.name = name; - this.requestSerde = requestSerde; - this.responseSerde = responseSerde; - } - - public static HandlerSignature of( - String method, Serde requestSerde, Serde responseSerde) { - return new HandlerSignature<>(method, requestSerde, responseSerde); - } - - public String getName() { - return name; - } - - public Serde getRequestSerde() { - return requestSerde; - } - - public Serde getResponseSerde() { - return responseSerde; - } - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/Context.java b/sdk-api/src/main/java/dev/restate/sdk/Context.java deleted file mode 100644 index 7e7cb35f..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/Context.java +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.function.ThrowingRunnable; -import dev.restate.sdk.common.function.ThrowingSupplier; -import java.time.Duration; - -/** - * This interface exposes the Restate functionalities to Restate services. It can be used to - * interact with other Restate services, record side effects, execute timers and synchronize with - * external systems. - * - *

All methods of this interface, and related interfaces, throws either {@link TerminalException} - * or {@link AbortedExecutionException}, where the former can be caught and acted upon, while the - * latter MUST NOT be caught, but simply propagated for clean up purposes. - * - *

NOTE: This interface MUST NOT be accessed concurrently since it can lead to different - * orderings of user actions, corrupting the execution of the invocation. - */ -public interface Context { - - /** - * @return this invocation id - */ - InvocationId invocationId(); - - /** - * Invoke another Restate service method. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param outputSerde Output serde - * @param parameter the invocation request parameter. - * @return an {@link Awaitable} that wraps the Restate service method result. - */ - Awaitable call(Target target, Serde inputSerde, Serde outputSerde, T parameter); - - /** Like {@link #call(Target, Serde, Serde, Object)} with raw input/output. */ - default Awaitable call(Target target, byte[] parameter) { - return call(target, CoreSerdes.RAW, CoreSerdes.RAW, parameter); - } - - /** - * Invoke another Restate service without waiting for the response. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param parameter the invocation request parameter. - */ - void send(Target target, Serde inputSerde, T parameter); - - /** Like {@link #send(Target, Serde, Object)} with raw input. */ - default void send(Target target, byte[] parameter) { - send(target, CoreSerdes.RAW, parameter); - } - - /** - * Invoke another Restate service without waiting for the response after the provided {@code - * delay} has elapsed. - * - *

This method returns immediately, as the timer is executed and awaited on Restate. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param parameter the invocation request parameter. - * @param delay time to wait before executing the call. - */ - void sendDelayed(Target target, Serde inputSerde, T parameter, Duration delay); - - /** Like {@link #sendDelayed(Target, Serde, Object, Duration)} with raw input. */ - default void sendDelayed(Target target, byte[] parameter, Duration delay) { - sendDelayed(target, CoreSerdes.RAW, parameter, delay); - } - - /** - * Causes the current execution of the function invocation to sleep for the given duration. - * - * @param duration for which to sleep. - */ - default void sleep(Duration duration) { - timer(duration).await(); - } - - /** - * Causes the start of a timer for the given duration. You can await on the timer end by invoking - * {@link Awaitable#await()}. - * - * @param duration for which to sleep. - */ - Awaitable timer(Duration duration); - - /** - * Execute a non-deterministic closure, recording the result value in the journal. The result - * value will be re-played in case of re-invocation (e.g. because of failure recovery or - * suspension point) without re-executing the closure. Use this feature if you want to perform - * non-deterministic operations. - * - *

The closure should tolerate retries, that is Restate might re-execute the closure multiple - * times until it records a result. - * - *

Error handling

- * - * Errors occurring within this closure won't be propagated to the caller, unless they are {@link - * TerminalException}. Consider the following code: - * - *
{@code
-   * // Bad usage of try-catch outside the side effect
-   * try {
-   *     ctx.sideEffect(() -> {
-   *         throw new IllegalStateException();
-   *     });
-   * } catch (IllegalStateException e) {
-   *     // This will never be executed,
-   *     // but the error will be retried by Restate,
-   *     // following the invocation retry policy.
-   * }
-   *
-   * // Good usage of try-catch outside the side effect
-   * try {
-   *     ctx.sideEffect(() -> {
-   *         throw new TerminalException("my error");
-   *     });
-   * } catch (TerminalException e) {
-   *     // This is invoked
-   * }
-   * }
- * - * To propagate side effects failures to the side effect call-site, make sure to wrap them in - * {@link TerminalException}. - * - * @param serde the type tag of the return value, used to serialize/deserialize it. - * @param action to execute for its side effects. - * @param type of the return value. - * @return value of the side effect operation. - */ - T sideEffect(Serde serde, ThrowingSupplier action) throws TerminalException; - - /** Like {@link #sideEffect(Serde, ThrowingSupplier)}, but without returning a value. */ - default void sideEffect(ThrowingRunnable runnable) throws TerminalException { - sideEffect( - CoreSerdes.VOID, - () -> { - runnable.run(); - return null; - }); - } - - /** - * Create an {@link Awakeable}, addressable through {@link Awakeable#id()}. - * - *

You can use this feature to implement external asynchronous systems interactions, for - * example you can send a Kafka record including the {@link Awakeable#id()}, and then let another - * service consume from Kafka the responses of given external system interaction by using {@link - * #awakeableHandle(String)}. - * - * @param serde the response type tag to use for deserializing the {@link Awakeable} result. - * @return the {@link Awakeable} to await on. - * @see Awakeable - */ - Awakeable awakeable(Serde serde); - - /** - * Create a new {@link AwakeableHandle} for the provided identifier. You can use it to {@link - * AwakeableHandle#resolve(Serde, Object)} or {@link AwakeableHandle#reject(String)} the linked - * {@link Awakeable}. - * - * @see Awakeable - */ - AwakeableHandle awakeableHandle(String id); - - /** - * @see RestateRandom - */ - RestateRandom random(); -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java b/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java deleted file mode 100644 index 321380bf..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.function.ThrowingSupplier; -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.EnterSideEffectSyscallCallback; -import dev.restate.sdk.common.syscalls.ExitSideEffectSyscallCallback; -import dev.restate.sdk.common.syscalls.Syscalls; -import java.time.Duration; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -class ContextImpl implements ObjectContext { - - private final Syscalls syscalls; - - ContextImpl(Syscalls syscalls) { - this.syscalls = syscalls; - } - - @Override - public String key() { - return syscalls.objectKey(); - } - - @Override - public InvocationId invocationId() { - return syscalls.invocationId(); - } - - @Override - public Optional get(StateKey key) { - Deferred deferred = Util.blockOnSyscall(cb -> syscalls.get(key.name(), cb)); - - if (!deferred.isCompleted()) { - Util.blockOnSyscall(cb -> syscalls.resolveDeferred(deferred, cb)); - } - - return Util.unwrapOptionalReadyResult(deferred.toResult()) - .map(bs -> Util.deserializeWrappingException(syscalls, key.serde(), bs)); - } - - @Override - public Collection stateKeys() { - Deferred> deferred = Util.blockOnSyscall(syscalls::getKeys); - - if (!deferred.isCompleted()) { - Util.blockOnSyscall(cb -> syscalls.resolveDeferred(deferred, cb)); - } - - return Util.unwrapResult(deferred.toResult()); - } - - @Override - public void clear(StateKey key) { - Util.blockOnSyscall(cb -> syscalls.clear(key.name(), cb)); - } - - @Override - public void clearAll() { - Util.blockOnSyscall(syscalls::clearAll); - } - - @Override - public void set(StateKey key, @NonNull T value) { - Util.blockOnSyscall( - cb -> - syscalls.set( - key.name(), Util.serializeWrappingException(syscalls, key.serde(), value), cb)); - } - - @Override - public Awaitable timer(Duration duration) { - Deferred result = Util.blockOnSyscall(cb -> syscalls.sleep(duration, cb)); - return Awaitable.single(syscalls, result); - } - - @Override - public Awaitable call( - Target target, Serde inputSerde, Serde outputSerde, T parameter) { - ByteString input = Util.serializeWrappingException(syscalls, inputSerde, parameter); - Deferred result = Util.blockOnSyscall(cb -> syscalls.call(target, input, cb)); - return Awaitable.single(syscalls, result) - .map(bs -> Util.deserializeWrappingException(syscalls, outputSerde, bs)); - } - - @Override - public void send(Target target, Serde inputSerde, T parameter) { - ByteString input = Util.serializeWrappingException(syscalls, inputSerde, parameter); - Util.blockOnSyscall(cb -> syscalls.send(target, input, null, cb)); - } - - @Override - public void sendDelayed(Target target, Serde inputSerde, T parameter, Duration delay) { - ByteString input = Util.serializeWrappingException(syscalls, inputSerde, parameter); - Util.blockOnSyscall(cb -> syscalls.send(target, input, delay, cb)); - } - - @Override - public T sideEffect(Serde serde, ThrowingSupplier action) { - CompletableFuture> enterFut = new CompletableFuture<>(); - syscalls.enterSideEffectBlock( - new EnterSideEffectSyscallCallback() { - @Override - public void onNotExecuted() { - enterFut.complete(new CompletableFuture<>()); - } - - @Override - public void onSuccess(ByteString result) { - enterFut.complete(CompletableFuture.completedFuture(result)); - } - - @Override - public void onFailure(TerminalException t) { - enterFut.complete(CompletableFuture.failedFuture(t)); - } - - @Override - public void onCancel(Throwable t) { - enterFut.cancel(true); - } - }); - - // If a failure was stored, it's simply thrown here - CompletableFuture exitFut = Util.awaitCompletableFuture(enterFut); - if (exitFut.isDone()) { - // We already have a result, we don't need to execute the action - return Util.deserializeWrappingException( - syscalls, serde, Util.awaitCompletableFuture(exitFut)); - } - - ExitSideEffectSyscallCallback exitCallback = - new ExitSideEffectSyscallCallback() { - @Override - public void onSuccess(ByteString result) { - exitFut.complete(result); - } - - @Override - public void onFailure(TerminalException t) { - exitFut.completeExceptionally(t); - } - - @Override - public void onCancel(@Nullable Throwable t) { - exitFut.cancel(true); - } - }; - - T res = null; - TerminalException failure = null; - try { - res = action.get(); - } catch (TerminalException e) { - failure = e; - } catch (Error e) { - throw e; - } catch (Throwable e) { - syscalls.fail(e); - AbortedExecutionException.sneakyThrow(); - } - - if (failure != null) { - syscalls.exitSideEffectBlockWithTerminalException(failure, exitCallback); - } else { - syscalls.exitSideEffectBlock( - Util.serializeWrappingException(syscalls, serde, res), exitCallback); - } - - return Util.deserializeWrappingException(syscalls, serde, Util.awaitCompletableFuture(exitFut)); - } - - @Override - public Awakeable awakeable(Serde serde) throws TerminalException { - // Retrieve the awakeable - Map.Entry> awakeable = Util.blockOnSyscall(syscalls::awakeable); - - return new Awakeable<>(syscalls, awakeable.getValue(), serde, awakeable.getKey()); - } - - @Override - public AwakeableHandle awakeableHandle(String id) { - return new AwakeableHandle() { - @Override - public void resolve(Serde serde, @NonNull T payload) { - Util.blockOnSyscall( - cb -> - syscalls.resolveAwakeable( - id, Util.serializeWrappingException(syscalls, serde, payload), cb)); - } - - @Override - public void reject(String reason) { - Util.blockOnSyscall(cb -> syscalls.rejectAwakeable(id, reason, cb)); - } - }; - } - - @Override - public RestateRandom random() { - return new RestateRandom(this.invocationId().toRandomSeed(), this.syscalls); - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java b/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java deleted file mode 100644 index 887ac603..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.*; -import java.util.Collection; -import java.util.Optional; -import org.jspecify.annotations.NonNull; - -/** - * This interface extends {@link Context} adding access to the virtual object instance key-value - * state storage - * - *

NOTE: This interface MUST NOT be accessed concurrently since it can lead to different - * orderings of user actions, corrupting the execution of the invocation. - * - * @see Context - */ -public interface ObjectContext extends Context { - - /** - * @return the key of this object - */ - String key(); - - /** - * Gets the state stored under key, deserializing the raw value using the {@link Serde} in the - * {@link StateKey}. - * - * @param key identifying the state to get and its type. - * @return an {@link Optional} containing the stored state deserialized or an empty {@link - * Optional} if not set yet. - * @throws RuntimeException when the state cannot be deserialized. - */ - Optional get(StateKey key); - - /** - * Gets all the known state keys for this virtual object instance. - * - * @return the immutable collection of known state keys. - */ - Collection stateKeys(); - - /** - * Clears the state stored under key. - * - * @param key identifying the state to clear. - */ - void clear(StateKey key); - - /** Clears all the state of this virtual object instance key-value state storage */ - void clearAll(); - - /** - * Sets the given value under the given key, serializing the value using the {@link Serde} in the - * {@link StateKey}. - * - * @param key identifying the value to store and its type. - * @param value to store under the given key. MUST NOT be null. - */ - void set(StateKey key, @NonNull T value); -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/RestateRandom.java b/sdk-api/src/main/java/dev/restate/sdk/RestateRandom.java deleted file mode 100644 index 4ed24ab1..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/RestateRandom.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.InvocationId; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.function.ThrowingSupplier; -import dev.restate.sdk.common.syscalls.Syscalls; -import java.util.Random; -import java.util.UUID; - -/** - * Subclass of {@link Random} inherently predictable, seeded on the {@link InvocationId}, which is - * not secret. - * - *

This instance is useful to generate identifiers, idempotency keys, and for uniform sampling - * from a set of options. If a cryptographically secure value is needed, please generate that - * externally using {@link ObjectContext#sideEffect(Serde, ThrowingSupplier)}. - * - *

You MUST NOT use this object inside a {@link ObjectContext#sideEffect(Serde, - * ThrowingSupplier)}. - */ -public class RestateRandom extends Random { - - private final Syscalls syscalls; - private boolean seedInitialized = false; - - RestateRandom(long randomSeed, Syscalls syscalls) { - super(randomSeed); - this.syscalls = syscalls; - } - - /** - * @throws UnsupportedOperationException You cannot set the seed on RestateRandom - */ - @Override - public synchronized void setSeed(long seed) { - if (seedInitialized) { - throw new UnsupportedOperationException("You cannot set the seed on RestateRandom"); - } - super.setSeed(seed); - this.seedInitialized = true; - } - - /** - * @return a UUID generated using this RNG. - */ - public UUID nextUUID() { - return new UUID(this.nextLong(), this.nextLong()); - } - - @Override - protected int next(int bits) { - if (this.syscalls.isInsideSideEffect()) { - throw new IllegalStateException("You can't use RestateRandom inside a side effect!"); - } - - return super.next(bits); - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/Util.java b/sdk-api/src/main/java/dev/restate/sdk/Util.java deleted file mode 100644 index fe055895..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/Util.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.AbortedExecutionException; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.function.ThrowingFunction; -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.Result; -import dev.restate.sdk.common.syscalls.SyscallCallback; -import dev.restate.sdk.common.syscalls.Syscalls; -import java.util.Optional; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; - -class Util { - - private Util() {} - - static T blockOnResolve(Syscalls syscalls, Deferred deferred) { - if (!deferred.isCompleted()) { - Util.blockOnSyscall(cb -> syscalls.resolveDeferred(deferred, cb)); - } - - return Util.unwrapResult(deferred.toResult()); - } - - static T awaitCompletableFuture(CompletableFuture future) { - try { - return future.get(); - } catch (InterruptedException | CancellationException e) { - AbortedExecutionException.sneakyThrow(); - return null; // Previous statement throws an exception - } catch (ExecutionException e) { - throw (RuntimeException) e.getCause(); - } - } - - static T blockOnSyscall(Consumer> syscallExecutor) { - CompletableFuture fut = new CompletableFuture<>(); - syscallExecutor.accept(SyscallCallback.completingFuture(fut)); - return Util.awaitCompletableFuture(fut); - } - - static T unwrapResult(Result res) { - if (res.isSuccess()) { - return res.getValue(); - } - throw res.getFailure(); - } - - static Optional unwrapOptionalReadyResult(Result res) { - if (!res.isSuccess()) { - throw res.getFailure(); - } - if (res.isEmpty()) { - return Optional.empty(); - } - return Optional.of(res.getValue()); - } - - static R executeMappingException(Syscalls syscalls, ThrowingFunction fn, T t) { - try { - return fn.apply(t); - } catch (Throwable e) { - syscalls.fail(e); - AbortedExecutionException.sneakyThrow(); - return null; - } - } - - static ByteString serializeWrappingException(Syscalls syscalls, Serde serde, T value) { - return executeMappingException(syscalls, serde::serializeToByteString, value); - } - - static T deserializeWrappingException( - Syscalls syscalls, Serde serde, ByteString byteString) { - return executeMappingException(syscalls, serde::deserialize, byteString); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java b/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java deleted file mode 100644 index 4d9d78c8..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.AwakeableIdTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; - -public class AwakeableIdTest extends AwakeableIdTestSuite { - - protected TestInvocationBuilder returnAwakeableId() { - return testDefinitionForService( - "ReturnAwakeableId", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> context.awakeable(CoreSerdes.JSON_STRING).id()); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/DeferredTest.java b/sdk-api/src/test/java/dev/restate/sdk/DeferredTest.java deleted file mode 100644 index 4e73bb1a..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/DeferredTest.java +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.*; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.core.DeferredTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.time.Duration; -import java.util.concurrent.TimeoutException; - -public class DeferredTest extends DeferredTestSuite { - - protected TestInvocationBuilder reverseAwaitOrder() { - return testDefinitionForVirtualObject( - "ReverseAwaitOrder", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> { - Awaitable a1 = callGreeterGreetService(context, "Francesco"); - Awaitable a2 = callGreeterGreetService(context, "Till"); - - String a2Res = a2.await(); - context.set(StateKey.of("A2", CoreSerdes.JSON_STRING), a2Res); - - String a1Res = a1.await(); - - return a1Res + "-" + a2Res; - }); - } - - protected TestInvocationBuilder awaitTwiceTheSameAwaitable() { - return testDefinitionForService( - "AwaitTwiceTheSameAwaitable", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> { - Awaitable a = callGreeterGreetService(context, "Francesco"); - - return a.await() + "-" + a.await(); - }); - } - - protected TestInvocationBuilder awaitAll() { - return testDefinitionForService( - "AwaitAll", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> { - Awaitable a1 = callGreeterGreetService(context, "Francesco"); - Awaitable a2 = callGreeterGreetService(context, "Till"); - - Awaitable.all(a1, a2).await(); - - return a1.await() + "-" + a2.await(); - }); - } - - protected TestInvocationBuilder awaitAny() { - return testDefinitionForService( - "AwaitAny", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> { - Awaitable a1 = callGreeterGreetService(context, "Francesco"); - Awaitable a2 = callGreeterGreetService(context, "Till"); - - return (String) Awaitable.any(a1, a2).await(); - }); - } - - protected TestInvocationBuilder combineAnyWithAll() { - return testDefinitionForService( - "CombineAnyWithAll", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a3 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a4 = ctx.awakeable(CoreSerdes.JSON_STRING); - - Awaitable a12 = Awaitable.any(a1, a2); - Awaitable a23 = Awaitable.any(a2, a3); - Awaitable a34 = Awaitable.any(a3, a4); - Awaitable.all(a12, a23, a34).await(); - - return a12.await() + (String) a23.await() + a34.await(); - }); - } - - protected TestInvocationBuilder awaitAnyIndex() { - return testDefinitionForService( - "AwaitAnyIndex", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a3 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a4 = ctx.awakeable(CoreSerdes.JSON_STRING); - - return String.valueOf(Awaitable.any(a1, Awaitable.all(a2, a3), a4).awaitIndex()); - }); - } - - protected TestInvocationBuilder awaitOnAlreadyResolvedAwaitables() { - return testDefinitionForService( - "AwaitOnAlreadyResolvedAwaitables", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); - - Awaitable a12 = Awaitable.all(a1, a2); - Awaitable a12and1 = Awaitable.all(a12, a1); - Awaitable a121and12 = Awaitable.all(a12and1, a12); - - a12and1.await(); - a121and12.await(); - - return a1.await() + a2.await(); - }); - } - - protected TestInvocationBuilder awaitWithTimeout() { - return testDefinitionForService( - "AwaitOnAlreadyResolvedAwaitables", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - Awaitable call = callGreeterGreetService(ctx, "Francesco"); - - String result; - try { - result = call.await(Duration.ofDays(1)); - } catch (TimeoutException e) { - result = "timeout"; - } - - return result; - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java b/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java deleted file mode 100644 index 19aba56c..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForVirtualObject; -import static org.assertj.core.api.Assertions.assertThat; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.core.EagerStateTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; - -public class EagerStateTest extends EagerStateTestSuite { - - protected TestInvocationBuilder getEmpty() { - return testDefinitionForVirtualObject( - "GetEmpty", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> - String.valueOf(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).isEmpty())); - } - - protected TestInvocationBuilder get() { - return testDefinitionForVirtualObject( - "GetEmpty", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get()); - } - - protected TestInvocationBuilder getAppendAndGet() { - return testDefinitionForVirtualObject( - "GetAppendAndGet", - CoreSerdes.JSON_STRING, - CoreSerdes.JSON_STRING, - (ctx, input) -> { - String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), oldState + input); - - return ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - }); - } - - protected TestInvocationBuilder getClearAndGet() { - return testDefinitionForVirtualObject( - "GetClearAndGet", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, input) -> { - String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - - ctx.clear(StateKey.of("STATE", CoreSerdes.JSON_STRING)); - assertThat(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))).isEmpty(); - return oldState; - }); - } - - protected TestInvocationBuilder getClearAllAndGet() { - return testDefinitionForVirtualObject( - "GetClearAllAndGet", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, input) -> { - String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - - ctx.clearAll(); - assertThat(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))).isEmpty(); - assertThat(ctx.get(StateKey.of("ANOTHER_STATE", CoreSerdes.JSON_STRING))).isEmpty(); - - return oldState; - }); - } - - protected TestInvocationBuilder listKeys() { - return testDefinitionForVirtualObject( - "ListKeys", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, input) -> String.join(",", ctx.stateKeys())); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java b/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java deleted file mode 100644 index 9c058be1..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.InvocationIdTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; - -public class InvocationIdTest extends InvocationIdTestSuite { - - protected TestInvocationBuilder returnInvocationId() { - return testDefinitionForService( - "ReturnInvocationId", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> ctx.invocationId().toString()); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/JavaBlockingTests.java b/sdk-api/src/test/java/dev/restate/sdk/JavaBlockingTests.java deleted file mode 100644 index 18b50258..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/JavaBlockingTests.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.core.ProtoUtils.GREETER_SERVICE_TARGET; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.core.MockMultiThreaded; -import dev.restate.sdk.core.MockSingleThread; -import dev.restate.sdk.core.TestDefinitions; -import dev.restate.sdk.core.TestDefinitions.TestExecutor; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import dev.restate.sdk.core.TestRunner; -import java.util.function.BiFunction; -import java.util.stream.Stream; - -public class JavaBlockingTests extends TestRunner { - - @Override - protected Stream executors() { - return Stream.of(MockSingleThread.INSTANCE, MockMultiThreaded.INSTANCE); - } - - @Override - public Stream definitions() { - return Stream.of( - new AwakeableIdTest(), - new DeferredTest(), - new EagerStateTest(), - new StateTest(), - new InvocationIdTest(), - new OnlyInputAndOutputTest(), - new SideEffectTest(), - new SleepTest(), - new StateMachineFailuresTest(), - new UserFailuresTest(), - new RandomTest()); - } - - public static TestInvocationBuilder testDefinitionForService( - String name, Serde reqSerde, Serde resSerde, BiFunction runner) { - return TestDefinitions.testInvocation( - Component.service(name) - .with(Component.HandlerSignature.of("run", reqSerde, resSerde), runner) - .build(), - "run"); - } - - public static TestInvocationBuilder testDefinitionForVirtualObject( - String name, Serde reqSerde, Serde resSerde, BiFunction runner) { - return TestDefinitions.testInvocation( - Component.virtualObject(name) - .with(Component.HandlerSignature.of("run", reqSerde, resSerde), runner) - .build(), - "run"); - } - - public static Awaitable callGreeterGreetService(Context ctx, String parameter) { - return ctx.call( - GREETER_SERVICE_TARGET, CoreSerdes.JSON_STRING, CoreSerdes.JSON_STRING, parameter); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java b/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java deleted file mode 100644 index c5a60447..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.OnlyInputAndOutputTestSuite; -import dev.restate.sdk.core.TestDefinitions; - -public class OnlyInputAndOutputTest extends OnlyInputAndOutputTestSuite { - - protected TestDefinitions.TestInvocationBuilder noSyscallsGreeter() { - return testDefinitionForService( - "NoSyscallsGreeter", - CoreSerdes.JSON_STRING, - CoreSerdes.JSON_STRING, - (ctx, input) -> "Hello " + input); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java b/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java deleted file mode 100644 index cb9f2f8a..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.RandomTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.util.Random; - -public class RandomTest extends RandomTestSuite { - - protected TestInvocationBuilder randomShouldBeDeterministic() { - return testDefinitionForService( - "RandomShouldBeDeterministic", - CoreSerdes.VOID, - CoreSerdes.JSON_INT, - (ctx, unused) -> ctx.random().nextInt()); - } - - protected TestInvocationBuilder randomInsideSideEffect() { - return testDefinitionForService( - "RandomInsideSideEffect", - CoreSerdes.VOID, - CoreSerdes.JSON_INT, - (ctx, unused) -> { - ctx.sideEffect(() -> ctx.random().nextInt()); - throw new IllegalStateException("This should not unreachable"); - }); - } - - protected int getExpectedInt(long seed) { - return new Random(seed).nextInt(); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java b/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java deleted file mode 100644 index 9f52ee23..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; -import static dev.restate.sdk.core.ProtoUtils.GREETER_SERVICE_TARGET; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.SideEffectTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.util.Objects; - -public class SideEffectTest extends SideEffectTestSuite { - - protected TestInvocationBuilder sideEffect(String sideEffectOutput) { - return testDefinitionForService( - "SideEffect", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String result = ctx.sideEffect(CoreSerdes.JSON_STRING, () -> sideEffectOutput); - return "Hello " + result; - }); - } - - protected TestInvocationBuilder consecutiveSideEffect(String sideEffectOutput) { - return testDefinitionForService( - "ConsecutiveSideEffect", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String firstResult = ctx.sideEffect(CoreSerdes.JSON_STRING, () -> sideEffectOutput); - String secondResult = ctx.sideEffect(CoreSerdes.JSON_STRING, firstResult::toUpperCase); - - return "Hello " + secondResult; - }); - } - - protected TestInvocationBuilder checkContextSwitching() { - return testDefinitionForService( - "CheckContextSwitching", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String currentThread = Thread.currentThread().getName(); - - String sideEffectThread = - ctx.sideEffect(CoreSerdes.JSON_STRING, () -> Thread.currentThread().getName()); - - if (!Objects.equals(currentThread, sideEffectThread)) { - throw new IllegalStateException( - "Current thread and side effect thread do not match: " - + currentThread - + " != " - + sideEffectThread); - } - - return "Hello"; - }); - } - - protected TestInvocationBuilder sideEffectGuard() { - return testDefinitionForService( - "SideEffectGuard", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.sideEffect(() -> ctx.send(GREETER_SERVICE_TARGET, new byte[] {})); - throw new IllegalStateException("This point should not be reached"); - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java b/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java deleted file mode 100644 index 306bc45e..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.SleepTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -public class SleepTest extends SleepTestSuite { - - protected TestInvocationBuilder sleepGreeter() { - return testDefinitionForService( - "SleepGreeter", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.sleep(Duration.ofSeconds(1)); - return "Hello"; - }); - } - - protected TestInvocationBuilder manySleeps() { - return testDefinitionForService( - "ManySleeps", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - List> collectedAwaitables = new ArrayList<>(); - - for (int i = 0; i < 10; i++) { - collectedAwaitables.add(ctx.timer(Duration.ofSeconds(1))); - } - - Awaitable.all( - collectedAwaitables.get(0), - collectedAwaitables.get(1), - collectedAwaitables - .subList(2, collectedAwaitables.size()) - .toArray(Awaitable[]::new)) - .await(); - - return null; - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java b/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java deleted file mode 100644 index f8b31352..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForVirtualObject; - -import dev.restate.sdk.common.*; -import dev.restate.sdk.core.StateMachineFailuresTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.atomic.AtomicInteger; - -public class StateMachineFailuresTest extends StateMachineFailuresTestSuite { - - private static final StateKey STATE = - StateKey.of( - "STATE", - Serde.using( - i -> Integer.toString(i).getBytes(StandardCharsets.UTF_8), - b -> Integer.parseInt(new String(b, StandardCharsets.UTF_8)))); - - protected TestInvocationBuilder getState(AtomicInteger nonTerminalExceptionsSeen) { - return testDefinitionForVirtualObject( - "GetState", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - try { - ctx.get(STATE); - } catch (Throwable e) { - // A user should never catch Throwable!!! - if (AbortedExecutionException.INSTANCE.equals(e)) { - AbortedExecutionException.sneakyThrow(); - } - if (!(e instanceof TerminalException)) { - nonTerminalExceptionsSeen.addAndGet(1); - } else { - throw e; - } - } - - return "Francesco"; - }); - } - - protected TestInvocationBuilder sideEffectFailure(Serde serde) { - return testDefinitionForVirtualObject( - "SideEffectFailure", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.sideEffect(serde, () -> 0); - return "Francesco"; - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/StateTest.java b/sdk-api/src/test/java/dev/restate/sdk/StateTest.java deleted file mode 100644 index 79ba15cb..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/StateTest.java +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForVirtualObject; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.core.StateTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; - -public class StateTest extends StateTestSuite { - - protected TestInvocationBuilder getState() { - return testDefinitionForVirtualObject( - "GetState", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).orElse("Unknown"); - - return "Hello " + state; - }); - } - - protected TestInvocationBuilder getAndSetState() { - return testDefinitionForVirtualObject( - "GetState", - CoreSerdes.JSON_STRING, - CoreSerdes.JSON_STRING, - (ctx, input) -> { - String state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - - ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), input); - - return "Hello " + state; - }); - } - - protected TestInvocationBuilder setNullState() { - return testDefinitionForVirtualObject( - "GetState", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.set( - StateKey.of( - "STATE", - Serde.using( - l -> { - throw new IllegalStateException("Unexpected call to serde fn"); - }, - l -> { - throw new IllegalStateException("Unexpected call to serde fn"); - })), - null); - - throw new IllegalStateException("set did not fail"); - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java b/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java deleted file mode 100644 index 81bf8ff7..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.AbortedExecutionException; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import dev.restate.sdk.core.UserFailuresTestSuite; -import java.util.concurrent.atomic.AtomicInteger; - -public class UserFailuresTest extends UserFailuresTestSuite { - - protected TestInvocationBuilder throwIllegalStateException() { - return testDefinitionForService( - "ThrowIllegalStateException", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - throw new IllegalStateException("Whatever"); - }); - } - - protected TestInvocationBuilder sideEffectThrowIllegalStateException( - AtomicInteger nonTerminalExceptionsSeen) { - return testDefinitionForService( - "SideEffectThrowIllegalStateException", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - try { - ctx.sideEffect( - () -> { - throw new IllegalStateException("Whatever"); - }); - } catch (Throwable e) { - // A user should never catch Throwable!!! - if (AbortedExecutionException.INSTANCE.equals(e)) { - AbortedExecutionException.sneakyThrow(); - } - if (!(e instanceof TerminalException)) { - nonTerminalExceptionsSeen.addAndGet(1); - } - throw e; - } - - throw new IllegalStateException("Unexpected end"); - }); - } - - protected TestInvocationBuilder throwTerminalException( - TerminalException.Code code, String message) { - return testDefinitionForService( - "ThrowTerminalException", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - throw new TerminalException(code, message); - }); - } - - protected TestInvocationBuilder sideEffectThrowTerminalException( - TerminalException.Code code, String message) { - return testDefinitionForService( - "SideEffectThrowTerminalException", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - ctx.sideEffect( - () -> { - throw new TerminalException(code, message); - }); - throw new IllegalStateException("This should not be reached"); - }); - } -} diff --git a/sdk-common/build.gradle.kts b/sdk-common/build.gradle.kts deleted file mode 100644 index 3f0a93d2..00000000 --- a/sdk-common/build.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` -} - -description = "Common interfaces of the Restate SDK" - -dependencies { - compileOnly(coreLibs.jspecify) - - api(coreLibs.protobuf.java) - - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.core) - - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java deleted file mode 100644 index 6fcecfd9..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -public @interface Exclusive {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java deleted file mode 100644 index feb2b8b7..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -public @interface Handler {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java deleted file mode 100644 index 8563d584..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) -public @interface Service { - /** - * Name of the Service for Restate. If not provided, it will be the FQCN of the annotated element. - */ - String name() default ""; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java deleted file mode 100644 index 3e205012..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -public @interface Shared {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java deleted file mode 100644 index 0af3e843..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) -public @interface VirtualObject { - /** - * Name of the VirtualObject for Restate. If not provided, it will be the FQCN of the annotated - * element. - */ - String name() default ""; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java deleted file mode 100644 index 0538292c..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.SOURCE) -public @interface Workflow { - /** - * Name of the Workflow for Restate. If not provided, it will be the FQCN of the annotated - * element. - */ - String name() default ""; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/client/DefaultIngressClient.java b/sdk-common/src/main/java/dev/restate/sdk/client/DefaultIngressClient.java deleted file mode 100644 index 32c1a533..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/client/DefaultIngressClient.java +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.client; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.Target; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; - -public class DefaultIngressClient implements IngressClient { - - private static final JsonFactory JSON_FACTORY = new JsonFactory(); - - private final HttpClient httpClient; - private final URI baseUri; - - public DefaultIngressClient(HttpClient httpClient, String baseUri) { - this.httpClient = httpClient; - this.baseUri = URI.create(baseUri); - } - - @Override - public Res call(Target target, Serde reqSerde, Serde resSerde, Req req) { - HttpRequest request = prepareHttpRequest(target, false, reqSerde, req); - HttpResponse response; - try { - response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray()); - } catch (IOException | InterruptedException e) { - throw new RuntimeException("Error when executing the request", e); - } - - if (response.statusCode() != 200) { - // Try to parse as string - String error = new String(response.body(), StandardCharsets.UTF_8); - throw new RuntimeException( - "Received non OK status code: " + response.statusCode() + ". Body: " + error); - } - - return resSerde.deserialize(response.body()); - } - - @Override - public String send(Target target, Serde reqSerde, Req req) { - HttpRequest request = prepareHttpRequest(target, true, reqSerde, req); - HttpResponse response; - try { - response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); - } catch (IOException | InterruptedException e) { - throw new RuntimeException("Error when executing the request", e); - } - - try (InputStream in = response.body()) { - if (response.statusCode() >= 300) { - // Try to parse as string - String error = new String(in.readAllBytes(), StandardCharsets.UTF_8); - throw new RuntimeException( - "Received non OK status code: " + response.statusCode() + ". Body: " + error); - } - return deserializeInvocationId(in); - } catch (IOException e) { - throw new RuntimeException( - "Error when trying to read the response, when status code was " + response.statusCode(), - e); - } - } - - private URI toRequestURI(Target target, boolean isSend) { - StringBuilder builder = new StringBuilder(); - builder.append("/").append(target.getComponent()); - if (target.getKey() != null) { - builder.append("/").append(target.getKey()); - } - builder.append("/").append(target.getHandler()); - if (isSend) { - builder.append("/send"); - } - - return this.baseUri.resolve(builder.toString()); - } - - private HttpRequest prepareHttpRequest( - Target target, boolean isSend, Serde reqSerde, Req req) { - var reqBuilder = HttpRequest.newBuilder().uri(toRequestURI(target, isSend)); - if (reqSerde.contentType() != null) { - reqBuilder.header("content-type", reqSerde.contentType()); - } - return reqBuilder.POST(HttpRequest.BodyPublishers.ofByteArray(reqSerde.serialize(req))).build(); - } - - private static String deserializeInvocationId(InputStream body) throws IOException { - try (JsonParser parser = JSON_FACTORY.createParser(body)) { - if (parser.nextToken() != JsonToken.START_OBJECT) { - throw new IllegalStateException( - "Expecting token " + JsonToken.START_OBJECT + ", got " + parser.getCurrentToken()); - } - String fieldName = parser.nextFieldName(); - if (fieldName == null || !fieldName.equalsIgnoreCase("invocationid")) { - throw new IllegalStateException( - "Expecting token \"invocationId\", got " + parser.getCurrentToken()); - } - String invocationId = parser.nextTextValue(); - if (invocationId == null) { - throw new IllegalStateException( - "Expecting token " + JsonToken.VALUE_STRING + ", got " + parser.getCurrentToken()); - } - return invocationId; - } - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/client/IngressClient.java b/sdk-common/src/main/java/dev/restate/sdk/client/IngressClient.java deleted file mode 100644 index 2b6d9125..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/client/IngressClient.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.client; - -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.Target; -import java.net.http.HttpClient; - -public interface IngressClient { - Res call(Target target, Serde reqSerde, Serde resSerde, Req req); - - String send(Target target, Serde reqSerde, Req req); - - static IngressClient defaultClient(String baseUri) { - return new DefaultIngressClient(HttpClient.newHttpClient(), baseUri); - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/AbortedExecutionException.java b/sdk-common/src/main/java/dev/restate/sdk/common/AbortedExecutionException.java deleted file mode 100644 index 80167f3d..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/AbortedExecutionException.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -/** You MUST NOT catch this exception. */ -public final class AbortedExecutionException extends Throwable { - @SuppressWarnings("StaticAssignmentOfThrowable") - public static final AbortedExecutionException INSTANCE = new AbortedExecutionException(); - - @SuppressWarnings("unchecked") - public static void sneakyThrow() throws E { - throw (E) AbortedExecutionException.INSTANCE; - } - - private AbortedExecutionException() { - super("AbortedExecutionException"); - setStackTrace(new StackTraceElement[] {}); - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/BindableComponent.java b/sdk-common/src/main/java/dev/restate/sdk/common/BindableComponent.java deleted file mode 100644 index 1b6b2fd3..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/BindableComponent.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import java.util.List; - -/** Marker interface for a Restate component. */ -public interface BindableComponent { - default List definitions() { - return null; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/ComponentAdapter.java b/sdk-common/src/main/java/dev/restate/sdk/common/ComponentAdapter.java deleted file mode 100644 index fc1d61ec..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/ComponentAdapter.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -public interface ComponentAdapter { - - BindableComponent adapt(T componentObject); - - boolean supportsObject(Object componentObject); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/ComponentType.java b/sdk-common/src/main/java/dev/restate/sdk/common/ComponentType.java deleted file mode 100644 index 60e18f2c..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/ComponentType.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -public enum ComponentType { - SERVICE, - VIRTUAL_OBJECT, - WORKFLOW -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/CoreSerdes.java b/sdk-common/src/main/java/dev/restate/sdk/common/CoreSerdes.java deleted file mode 100644 index a4b3c5e8..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/CoreSerdes.java +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.MessageLite; -import com.google.protobuf.Parser; -import dev.restate.sdk.common.function.ThrowingBiConsumer; -import dev.restate.sdk.common.function.ThrowingFunction; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Objects; -import org.jspecify.annotations.Nullable; - -/** - * Collection of common serializers/deserializers. - * - *

To ser/de POJOs using JSON, you can use the module {@code sdk-serde-jackson}. - */ -public abstract class CoreSerdes { - - private CoreSerdes() {} - - /** Noop {@link Serde} for void. */ - public static Serde VOID = - new Serde<>() { - @Override - public byte[] serialize(Void value) { - return new byte[0]; - } - - @Override - public ByteString serializeToByteString(@Nullable Void value) { - return ByteString.EMPTY; - } - - @Override - public Void deserialize(byte[] value) { - return null; - } - - @Override - public Void deserialize(ByteString byteString) { - return null; - } - }; - - /** Pass through {@link Serde} for byte array. */ - public static Serde RAW = - new Serde<>() { - @Override - public byte[] serialize(byte[] value) { - return Objects.requireNonNull(value); - } - - @Override - public byte[] deserialize(byte[] value) { - return value; - } - }; - - /** {@link Serde} for {@link String}. This writes and reads {@link String} as JSON value. */ - public static Serde JSON_STRING = - usingJackson( - JsonGenerator::writeString, - p -> { - if (p.nextToken() != JsonToken.VALUE_STRING) { - throw new IllegalStateException( - "Expecting token " + JsonToken.VALUE_STRING + ", got " + p.getCurrentToken()); - } - return p.getText(); - }); - - /** {@link Serde} for {@link Boolean}. This writes and reads {@link Boolean} as JSON value. */ - public static Serde JSON_BOOLEAN = - usingJackson( - JsonGenerator::writeBoolean, - p -> { - p.nextToken(); - return p.getBooleanValue(); - }); - - /** {@link Serde} for {@link Byte}. This writes and reads {@link Byte} as JSON value. */ - public static Serde JSON_BYTE = - usingJackson( - JsonGenerator::writeNumber, - p -> { - p.nextToken(); - return p.getByteValue(); - }); - - /** {@link Serde} for {@link Short}. This writes and reads {@link Short} as JSON value. */ - public static Serde JSON_SHORT = - usingJackson( - JsonGenerator::writeNumber, - p -> { - p.nextToken(); - return p.getShortValue(); - }); - - /** {@link Serde} for {@link Integer}. This writes and reads {@link Integer} as JSON value. */ - public static Serde JSON_INT = - usingJackson( - JsonGenerator::writeNumber, - p -> { - p.nextToken(); - return p.getIntValue(); - }); - - /** {@link Serde} for {@link Long}. This writes and reads {@link Long} as JSON value. */ - public static Serde JSON_LONG = - usingJackson( - JsonGenerator::writeNumber, - p -> { - p.nextToken(); - return p.getLongValue(); - }); - - /** {@link Serde} for {@link Float}. This writes and reads {@link Float} as JSON value. */ - public static Serde JSON_FLOAT = - usingJackson( - JsonGenerator::writeNumber, - p -> { - p.nextToken(); - return p.getFloatValue(); - }); - - /** {@link Serde} for {@link Double}. This writes and reads {@link Double} as JSON value. */ - public static Serde JSON_DOUBLE = - usingJackson( - JsonGenerator::writeNumber, - p -> { - p.nextToken(); - return p.getDoubleValue(); - }); - - public static Serde ofProtobuf(Parser parser) { - return new Serde<>() { - @Override - public byte[] serialize(@Nullable T value) { - return Objects.requireNonNull(value).toByteArray(); - } - - @Override - public T deserialize(byte[] value) { - try { - return parser.parseFrom(value); - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException("Cannot deserialize Protobuf object", e); - } - } - - // -- We reimplement the ByteString variants here as it might be more efficient to use them. - @Override - public ByteString serializeToByteString(@Nullable T value) { - return Objects.requireNonNull(value).toByteString(); - } - - @Override - public T deserialize(ByteString byteString) { - try { - return parser.parseFrom(byteString); - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException("Cannot deserialize Protobuf object", e); - } - } - }; - } - - // --- Helpers for jackson-core - - private static final JsonFactory JSON_FACTORY = new JsonFactory(); - - private static Serde usingJackson( - ThrowingBiConsumer serializer, - ThrowingFunction deserializer) { - return new Serde<>() { - @Override - public byte[] serialize(@Nullable T value) { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (JsonGenerator gen = JSON_FACTORY.createGenerator(outputStream)) { - serializer.asBiConsumer().accept(gen, value); - } catch (IOException e) { - throw new RuntimeException("Cannot create JsonGenerator", e); - } - return outputStream.toByteArray(); - } - - @Override - public T deserialize(byte[] value) { - ByteArrayInputStream inputStream = new ByteArrayInputStream(value); - try (JsonParser parser = JSON_FACTORY.createParser(inputStream)) { - return deserializer.asFunction().apply(parser); - } catch (IOException e) { - throw new RuntimeException("Cannot create JsonGenerator", e); - } - } - }; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/InvocationId.java b/sdk-common/src/main/java/dev/restate/sdk/common/InvocationId.java deleted file mode 100644 index 8b9b409b..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/InvocationId.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -/** - * This represents a stable identifier created by Restate for this invocation. It can be used as - * idempotency key when accessing external systems. - * - *

You can embed it in external system requests by using {@link #toString()}. - */ -public interface InvocationId { - - /** - * @return a seed to be used with {@link java.util.Random}. - */ - long toRandomSeed(); - - @Override - String toString(); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java b/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java deleted file mode 100644 index df74ebcd..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import com.google.protobuf.ByteString; -import com.google.protobuf.UnsafeByteOperations; -import dev.restate.sdk.common.function.ThrowingFunction; -import java.util.Objects; -import org.jspecify.annotations.Nullable; - -/** Interface defining serialization and deserialization of concrete types. */ -public interface Serde { - - byte[] serialize(@Nullable T value); - - default ByteString serializeToByteString(@Nullable T value) { - // This is safe because we don't mutate the generated byte[] afterward. - return UnsafeByteOperations.unsafeWrap(serialize(value)); - } - - T deserialize(byte[] value); - - default T deserialize(ByteString byteString) { - return deserialize(byteString.toByteArray()); - } - - /** - * @return the schema of this object. - */ - default @Nullable Object schema() { - return null; - } - - default @Nullable String contentType() { - return null; - } - - /** - * Create a {@link Serde} from {@code serializer}/{@code deserializer} lambdas. Before invoking - * the serializer, we check that {@code value} is non-null. - */ - static Serde using( - ThrowingFunction serializer, ThrowingFunction deserializer) { - return new Serde<>() { - @Override - public byte[] serialize(T value) { - return serializer.asFunction().apply(Objects.requireNonNull(value)); - } - - @Override - public T deserialize(byte[] value) { - return deserializer.asFunction().apply(value); - } - }; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/StateKey.java b/sdk-common/src/main/java/dev/restate/sdk/common/StateKey.java deleted file mode 100644 index bc54cf0e..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/StateKey.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -/** - * This class holds information about state's name and its type tag to be used for serializing and - * deserializing it. - * - * @param the generic type of the state. - */ -public final class StateKey { - - private final String name; - private final Serde serde; - - private StateKey(String name, Serde serde) { - this.name = name; - this.serde = serde; - } - - /** Create a new {@link StateKey}. */ - public static StateKey of(String name, Serde serde) { - return new StateKey<>(name, serde); - } - - /** Create a new {@link StateKey} for {@link String} state. */ - public static StateKey string(String name) { - return new StateKey<>(name, CoreSerdes.JSON_STRING); - } - - /** Create a new {@link StateKey} for bytes state. */ - public static StateKey raw(String name) { - return new StateKey<>(name, CoreSerdes.RAW); - } - - public String name() { - return name; - } - - public Serde serde() { - return serde; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Target.java b/sdk-common/src/main/java/dev/restate/sdk/common/Target.java deleted file mode 100644 index 6b750a7c..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/Target.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import java.util.Objects; - -public final class Target { - - private final String component; - private final String handler; - private final String key; - - private Target(String component, String handler, String key) { - this.component = component; - this.handler = handler; - this.key = key; - } - - public static Target virtualObject(String name, String key, String handler) { - return new Target(name, handler, key); - } - - public static Target service(String name, String handler) { - return new Target(name, handler, null); - } - - public String getComponent() { - return component; - } - - public String getHandler() { - return handler; - } - - public String getKey() { - return key; - } - - @Override - public boolean equals(Object object) { - if (this == object) return true; - if (object == null || getClass() != object.getClass()) return false; - Target target = (Target) object; - return Objects.equals(component, target.component) - && Objects.equals(handler, target.handler) - && Objects.equals(key, target.key); - } - - @Override - public int hashCode() { - return Objects.hash(component, handler, key); - } - - @Override - public String toString() { - if (key == null) { - return component + "/" + handler; - } - return component + "/" + key + "/" + handler; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/TerminalException.java b/sdk-common/src/main/java/dev/restate/sdk/common/TerminalException.java deleted file mode 100644 index 5acd837a..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/TerminalException.java +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -/** When thrown in a Restate service method, it will complete the invocation with an error. */ -public class TerminalException extends RuntimeException { - - public enum Code { - OK(0), - CANCELLED(1), - UNKNOWN(2), - INVALID_ARGUMENT(3), - DEADLINE_EXCEEDED(4), - NOT_FOUND(5), - ALREADY_EXISTS(6), - PERMISSION_DENIED(7), - RESOURCE_EXHAUSTED(8), - FAILED_PRECONDITION(9), - ABORTED(10), - OUT_OF_RANGE(11), - UNIMPLEMENTED(12), - INTERNAL(13), - UNAVAILABLE(14), - DATA_LOSS(15), - UNAUTHENTICATED(16); - - private final int value; - - Code(int value) { - this.value = value; - } - - /** The numerical value of the code. */ - public int value() { - return value; - } - - public static Code fromValue(int value) { - switch (value) { - case 0: - return Code.OK; - case 1: - return Code.CANCELLED; - case 2: - return Code.UNKNOWN; - case 3: - return Code.INVALID_ARGUMENT; - case 4: - return Code.DEADLINE_EXCEEDED; - case 5: - return Code.NOT_FOUND; - case 6: - return Code.ALREADY_EXISTS; - case 7: - return Code.PERMISSION_DENIED; - case 8: - return Code.RESOURCE_EXHAUSTED; - case 9: - return Code.FAILED_PRECONDITION; - case 10: - return Code.ABORTED; - case 11: - return Code.OUT_OF_RANGE; - case 12: - return Code.UNIMPLEMENTED; - case 13: - return Code.INTERNAL; - case 14: - return Code.UNAVAILABLE; - case 15: - return Code.DATA_LOSS; - case 16: - return Code.UNAUTHENTICATED; - default: - return Code.UNKNOWN; - } - } - } - - private final Code code; - - public TerminalException() { - this.code = Code.UNKNOWN; - } - - public TerminalException(Code code) { - this.code = code; - } - - public TerminalException(Code code, String message) { - super(message); - this.code = code; - } - - public TerminalException(String message) { - super(message); - this.code = Code.UNKNOWN; - } - - public Code getCode() { - return code; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingBiConsumer.java b/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingBiConsumer.java deleted file mode 100644 index 30bade48..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingBiConsumer.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.function; - -import java.util.function.BiConsumer; - -/** Like {@link BiConsumer} but can throw checked exceptions. */ -@FunctionalInterface -public interface ThrowingBiConsumer { - void accept(T var1, U var2) throws Throwable; - - static BiConsumer wrap(ThrowingBiConsumer fn) { - return fn.asBiConsumer(); - } - - default BiConsumer asBiConsumer() { - return (t, u) -> { - try { - this.accept(t, u); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } catch (Throwable e) { - // Make sure we propagate java.lang.Error - sneakyThrow(e); - } - }; - } - - @SuppressWarnings("unchecked") - private static void sneakyThrow(Throwable e) throws E { - throw (E) e; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingFunction.java b/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingFunction.java deleted file mode 100644 index 032adeac..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingFunction.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.function; - -import java.util.function.Function; - -/** Like {@link java.util.function.Function} but can throw checked exceptions. */ -@FunctionalInterface -public interface ThrowingFunction { - R apply(T var1) throws Throwable; - - static Function wrap(ThrowingFunction fn) { - return fn.asFunction(); - } - - default Function asFunction() { - return t -> { - try { - return this.apply(t); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } catch (Throwable e) { - // Make sure we propagate java.lang.Error - sneakyThrow(e); - return null; - } - }; - } - - @SuppressWarnings("unchecked") - private static void sneakyThrow(Throwable e) throws E { - throw (E) e; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingRunnable.java b/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingRunnable.java deleted file mode 100644 index 8f95b42c..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingRunnable.java +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.function; - -/** Like {@link Runnable} but can throw checked exceptions. */ -@FunctionalInterface -public interface ThrowingRunnable { - - /** Run, potentially throwing an exception. */ - void run() throws Throwable; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingSupplier.java b/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingSupplier.java deleted file mode 100644 index 775bc5d0..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingSupplier.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.function; - -/** Like {@link java.util.function.Supplier} but can throw checked exceptions. */ -@FunctionalInterface -public interface ThrowingSupplier { - - /** - * Get a result, potentially throwing an exception. - * - * @return a result - */ - T get() throws Throwable; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ComponentDefinition.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ComponentDefinition.java deleted file mode 100644 index 580eea02..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ComponentDefinition.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import dev.restate.sdk.common.ComponentType; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - -public final class ComponentDefinition { - - private final String fullyQualifiedServiceName; - private final ExecutorType executorType; - private final ComponentType componentType; - private final Map handlers; - - public ComponentDefinition( - String fullyQualifiedServiceName, - ExecutorType executorType, - ComponentType componentType, - Collection handlers) { - this.fullyQualifiedServiceName = fullyQualifiedServiceName; - this.executorType = executorType; - this.componentType = componentType; - this.handlers = - handlers.stream() - .collect(Collectors.toMap(HandlerDefinition::getName, Function.identity())); - } - - public String getFullyQualifiedServiceName() { - return fullyQualifiedServiceName; - } - - public ExecutorType getExecutorType() { - return executorType; - } - - public ComponentType getComponentType() { - return componentType; - } - - public Collection getHandlers() { - return handlers.values(); - } - - public HandlerDefinition getHandler(String name) { - return handlers.get(name); - } - - @Override - public boolean equals(Object object) { - if (this == object) return true; - if (object == null || getClass() != object.getClass()) return false; - ComponentDefinition that = (ComponentDefinition) object; - return Objects.equals(fullyQualifiedServiceName, that.fullyQualifiedServiceName) - && executorType == that.executorType - && componentType == that.componentType - && Objects.equals(handlers, that.handlers); - } - - @Override - public int hashCode() { - return Objects.hash(fullyQualifiedServiceName, executorType, componentType, handlers); - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Deferred.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Deferred.java deleted file mode 100644 index 9b93148f..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Deferred.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import org.jspecify.annotations.Nullable; - -/** - * Interface to define interaction with deferred results. - * - *

Implementations of this class are provided by {@link Syscalls} and should not be - * overriden/wrapped. - * - *

To resolve a {@link Deferred}, use {@link Syscalls#resolveDeferred(Deferred, SyscallCallback)} - */ -public interface Deferred { - - boolean isCompleted(); - - /** - * @return {@code null} if {@link #isCompleted()} is false. - */ - @Nullable Result toResult(); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/EnterSideEffectSyscallCallback.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/EnterSideEffectSyscallCallback.java deleted file mode 100644 index 9559e0b4..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/EnterSideEffectSyscallCallback.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -public interface EnterSideEffectSyscallCallback extends ExitSideEffectSyscallCallback { - - void onNotExecuted(); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExecutorType.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExecutorType.java deleted file mode 100644 index 6540d5c9..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExecutorType.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -public enum ExecutorType { - BLOCKING, - NON_BLOCKING -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExitSideEffectSyscallCallback.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExitSideEffectSyscallCallback.java deleted file mode 100644 index 1f590a37..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExitSideEffectSyscallCallback.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.TerminalException; - -public interface ExitSideEffectSyscallCallback extends SyscallCallback { - - /** This is user failure. */ - void onFailure(TerminalException t); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/HandlerDefinition.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/HandlerDefinition.java deleted file mode 100644 index 0fb8ac34..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/HandlerDefinition.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import java.util.Objects; - -public final class HandlerDefinition { - private final String name; - private final Object inputSchema; - private final Object outputSchema; - private final InvocationHandler handler; - - public HandlerDefinition( - String name, Object inputSchema, Object outputSchema, InvocationHandler handler) { - this.name = name; - this.inputSchema = inputSchema; - this.outputSchema = outputSchema; - this.handler = handler; - } - - public String getName() { - return name; - } - - public Object getInputSchema() { - return inputSchema; - } - - public Object getOutputSchema() { - return outputSchema; - } - - public InvocationHandler getHandler() { - return handler; - } - - @Override - public boolean equals(Object object) { - if (this == object) return true; - if (object == null || getClass() != object.getClass()) return false; - HandlerDefinition that = (HandlerDefinition) object; - return Objects.equals(name, that.name) - && Objects.equals(inputSchema, that.inputSchema) - && Objects.equals(outputSchema, that.outputSchema) - && Objects.equals(handler, that.handler); - } - - @Override - public int hashCode() { - return Objects.hash(name, inputSchema, outputSchema, handler); - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java deleted file mode 100644 index 4c8e1235..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import com.google.protobuf.ByteString; - -public interface InvocationHandler { - - void handle(Syscalls syscalls, ByteString input, SyscallCallback callback); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Result.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Result.java deleted file mode 100644 index 74f76115..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Result.java +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import dev.restate.sdk.common.TerminalException; -import java.util.function.Function; -import org.jspecify.annotations.Nullable; - -/** - * Result can be 3 valued: - * - *

    - *
  • Empty - *
  • Value - *
  • Failure - *
- * - * Empty and Value are used to distinguish the logical empty with the null result. - * - *

Failure in a ready result is always a user failure, and never a syscall failure, as opposed to - * {@link SyscallCallback#onCancel(Throwable)}. - * - * @param result type - */ -public abstract class Result { - - private Result() {} - - /** - * @return true if there is no failure. - */ - public abstract boolean isSuccess(); - - public abstract boolean isEmpty(); - - /** - * @return The success value, or null in case is empty. - */ - @Nullable - public abstract T getValue(); - - @Nullable - public abstract TerminalException getFailure(); - - // --- Helper methods - - /** - * Map this result success value. If the mapper throws an exception, this exception will be - * converted to {@link TerminalException} and return a new failed {@link Result}. - */ - public Result mapSuccess(Function mapper) { - if (this.isSuccess()) { - try { - return Result.success(mapper.apply(this.getValue())); - } catch (TerminalException e) { - return Result.failure(e); - } catch (Exception e) { - return Result.failure( - new TerminalException(TerminalException.Code.UNKNOWN, e.getMessage())); - } - } - //noinspection unchecked - return (Result) this; - } - - // --- Factory methods - - @SuppressWarnings("unchecked") - public static Result empty() { - return (Result) Empty.INSTANCE; - } - - public static Result success(T value) { - return new Success<>(value); - } - - public static Result failure(TerminalException t) { - return new Failure<>(t); - } - - static class Empty extends Result { - - public static Empty INSTANCE = new Empty<>(); - - private Empty() {} - - @Override - public boolean isSuccess() { - return true; - } - - @Override - public boolean isEmpty() { - return true; - } - - @Nullable - @Override - public T getValue() { - return null; - } - - @Nullable - @Override - public TerminalException getFailure() { - return null; - } - } - - static class Success extends Result { - private final T value; - - private Success(T value) { - this.value = value; - } - - @Override - public boolean isSuccess() { - return true; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Nullable - @Override - public T getValue() { - return value; - } - - @Nullable - @Override - public TerminalException getFailure() { - return null; - } - } - - static class Failure extends Result { - private final TerminalException cause; - - private Failure(TerminalException cause) { - this.cause = cause; - } - - @Override - public boolean isSuccess() { - return false; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Nullable - @Override - public T getValue() { - return null; - } - - @Nullable - @Override - public TerminalException getFailure() { - return cause; - } - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/SyscallCallback.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/SyscallCallback.java deleted file mode 100644 index cc882b5f..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/SyscallCallback.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; -import org.jspecify.annotations.Nullable; - -public interface SyscallCallback { - - void onSuccess(@Nullable T value); - - /** - * The internal state machine invokes this method when a syscall is interrupted due to a - * suspension, or a network error. - * - *

In case the user code is blocked on a lock, the implementation of this method should unblock - * it. - */ - void onCancel(Throwable t); - - static SyscallCallback of(Consumer onSuccess, Consumer onFailure) { - return new SyscallCallback<>() { - @Override - public void onSuccess(@Nullable T value) { - onSuccess.accept(value); - } - - @Override - public void onCancel(@Nullable Throwable t) { - onFailure.accept(t); - } - }; - } - - static SyscallCallback ofVoid(Runnable onSuccess, Consumer onFailure) { - return new SyscallCallback<>() { - @Override - public void onSuccess(@Nullable Void value) { - onSuccess.run(); - } - - @Override - public void onCancel(@Nullable Throwable t) { - onFailure.accept(t); - } - }; - } - - static SyscallCallback mappingTo(Function mapper, SyscallCallback callback) { - return new SyscallCallback<>() { - @Override - public void onSuccess(@Nullable T value) { - callback.onSuccess(mapper.apply(value)); - } - - @Override - public void onCancel(@Nullable Throwable t) { - callback.onCancel(t); - } - }; - } - - static SyscallCallback completingFuture(CompletableFuture fut) { - return of(fut::complete, t -> fut.cancel(true)); - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Syscalls.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Syscalls.java deleted file mode 100644 index 174b022f..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Syscalls.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.InvocationId; -import dev.restate.sdk.common.Target; -import dev.restate.sdk.common.TerminalException; -import java.time.Duration; -import java.util.*; -import java.util.List; -import java.util.Map; -import org.jspecify.annotations.Nullable; - -/** - * Internal interface to access Restate functionalities. Users can use the ad-hoc RestateContext - * interfaces provided by the various implementations. - * - *

When using executor switching wrappers, the method's {@code callback} will be executed in the - * state machine executor. - */ -public interface Syscalls { - - InvocationId invocationId(); - - String objectKey(); - - /** - * @return true if it's inside a side effect block. - */ - boolean isInsideSideEffect(); - - // ----- IO - // Note: These are not supposed to be exposed to RestateContext, but they should be used through - // gRPC APIs. - - void pollInput(SyscallCallback> callback); - - void writeOutput(ByteString value, SyscallCallback callback); - - void writeOutput(TerminalException exception, SyscallCallback callback); - - // ----- State - - void get(String name, SyscallCallback> callback); - - void getKeys(SyscallCallback>> callback); - - void clear(String name, SyscallCallback callback); - - void clearAll(SyscallCallback callback); - - void set(String name, ByteString value, SyscallCallback callback); - - // ----- Syscalls - - void sleep(Duration duration, SyscallCallback> callback); - - void call(Target target, ByteString parameter, SyscallCallback> callback); - - void send( - Target target, - ByteString parameter, - @Nullable Duration delay, - SyscallCallback requestCallback); - - void enterSideEffectBlock(EnterSideEffectSyscallCallback callback); - - void exitSideEffectBlock(ByteString toWrite, ExitSideEffectSyscallCallback callback); - - void exitSideEffectBlockWithTerminalException( - TerminalException toWrite, ExitSideEffectSyscallCallback callback); - - void awakeable(SyscallCallback>> callback); - - void resolveAwakeable(String id, ByteString payload, SyscallCallback requestCallback); - - void rejectAwakeable(String id, String reason, SyscallCallback requestCallback); - - void fail(Throwable cause); - - // ----- Deferred - - void resolveDeferred(Deferred deferredToResolve, SyscallCallback callback); - - Deferred createAnyDeferred(List> children); - - Deferred createAllDeferred(List> children); -} diff --git a/sdk-common/src/test/java/dev/restate/sdk/common/CoreSerdesTest.java b/sdk-common/src/test/java/dev/restate/sdk/common/CoreSerdesTest.java deleted file mode 100644 index 7eafbe30..00000000 --- a/sdk-common/src/test/java/dev/restate/sdk/common/CoreSerdesTest.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.nio.charset.StandardCharsets; -import java.util.Random; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class CoreSerdesTest { - - private static Arguments roundtripCase(Serde serde, T value) { - return Arguments.of( - (value != null ? value.getClass().getSimpleName() : "Null") + ": " + value, serde, value); - } - - private static Stream roundtrip() { - var random = new Random(); - return Stream.of( - roundtripCase(CoreSerdes.VOID, null), - roundtripCase(CoreSerdes.RAW, new byte[] {1, 2, 3, 4}), - roundtripCase(CoreSerdes.JSON_STRING, ""), - roundtripCase(CoreSerdes.JSON_STRING, "Francesco1234"), - roundtripCase(CoreSerdes.JSON_STRING, "πŸ˜€"), - roundtripCase(CoreSerdes.JSON_BOOLEAN, true), - roundtripCase(CoreSerdes.JSON_BOOLEAN, false), - roundtripCase(CoreSerdes.JSON_BYTE, Byte.MIN_VALUE), - roundtripCase(CoreSerdes.JSON_BYTE, Byte.MAX_VALUE), - roundtripCase(CoreSerdes.JSON_BYTE, (byte) random.nextInt()), - roundtripCase(CoreSerdes.JSON_SHORT, Short.MIN_VALUE), - roundtripCase(CoreSerdes.JSON_SHORT, Short.MAX_VALUE), - roundtripCase(CoreSerdes.JSON_SHORT, (short) random.nextInt()), - roundtripCase(CoreSerdes.JSON_INT, Integer.MIN_VALUE), - roundtripCase(CoreSerdes.JSON_INT, Integer.MAX_VALUE), - roundtripCase(CoreSerdes.JSON_INT, random.nextInt()), - roundtripCase(CoreSerdes.JSON_LONG, Long.MIN_VALUE), - roundtripCase(CoreSerdes.JSON_LONG, Long.MAX_VALUE), - roundtripCase(CoreSerdes.JSON_LONG, random.nextLong()), - roundtripCase(CoreSerdes.JSON_FLOAT, Float.MIN_VALUE), - roundtripCase(CoreSerdes.JSON_FLOAT, Float.MAX_VALUE), - roundtripCase(CoreSerdes.JSON_FLOAT, random.nextFloat()), - roundtripCase(CoreSerdes.JSON_DOUBLE, Double.MIN_VALUE), - roundtripCase(CoreSerdes.JSON_DOUBLE, Double.MAX_VALUE), - roundtripCase(CoreSerdes.JSON_DOUBLE, random.nextDouble())); - } - - @ParameterizedTest(name = "{0}") - @MethodSource - void roundtrip(String testName, Serde serde, T value) throws Throwable { - assertThat(serde.deserialize(serde.serialize(value))).isEqualTo(value); - } - - private static Stream failDeserialization() { - return Stream.of( - Arguments.of("String unquoted", CoreSerdes.JSON_STRING, "my string"), - Arguments.of("Not a boolean", CoreSerdes.JSON_BOOLEAN, "something"), - Arguments.of("Not a byte", CoreSerdes.JSON_BYTE, "something"), - Arguments.of("Not a short", CoreSerdes.JSON_SHORT, "something"), - Arguments.of("Not a int", CoreSerdes.JSON_INT, "something"), - Arguments.of("Not a long", CoreSerdes.JSON_LONG, "something"), - Arguments.of("Not a float", CoreSerdes.JSON_FLOAT, "something"), - Arguments.of("Not a double", CoreSerdes.JSON_DOUBLE, "something")); - } - - @ParameterizedTest(name = "{0}") - @MethodSource - void failDeserialization(String testName, Serde serde, String value) throws Throwable { - assertThatThrownBy(() -> serde.deserialize(value.getBytes(StandardCharsets.UTF_8))).isNotNull(); - } -} diff --git a/sdk-core/build.gradle.kts b/sdk-core/build.gradle.kts deleted file mode 100644 index 46dc1853..00000000 --- a/sdk-core/build.gradle.kts +++ /dev/null @@ -1,83 +0,0 @@ -import com.google.protobuf.gradle.id - -plugins { - `java-library` - `library-publishing-conventions` - id("org.jsonschema2pojo") version "1.2.1" - alias(pluginLibs.plugins.protobuf) -} - -description = "Restate SDK Core" - -dependencies { - compileOnly(coreLibs.jspecify) - - implementation(project(":sdk-common")) - - implementation(coreLibs.protobuf.java) - implementation(coreLibs.log4j.api) - - // We need this for the manifest - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.annotations) - - // We don't want a hard-dependency on it - compileOnly(coreLibs.log4j.core) - - implementation(platform(coreLibs.opentelemetry.bom)) - implementation(coreLibs.opentelemetry.api) - implementation(coreLibs.opentelemetry.semconv) - - testCompileOnly(coreLibs.jspecify) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(coreLibs.log4j.core) -} - -// Configure source sets for protobuf plugin and jsonschema2pojo -val generatedJ2SPDir = layout.buildDirectory.dir("generated/j2sp") - -sourceSets { - main { - java.srcDir(generatedJ2SPDir) - proto { srcDirs("src/main/sdk-proto", "src/main/service-protocol") } - } -} - -// Configure jsonSchema2Pojo -jsonSchema2Pojo { - setSource(files("$projectDir/src/main/service-protocol/deployment_manifest_schema.json")) - targetPackage = "dev.restate.sdk.core.manifest" - targetDirectory = generatedJ2SPDir.get().asFile - - useLongIntegers = false - includeSetters = true - includeGetters = true - generateBuilders = true -} - -// Configure protobuf - -val protobufVersion = coreLibs.versions.protobuf.get() - -protobuf { protoc { artifact = "com.google.protobuf:protoc:$protobufVersion" } } - -// Make sure task dependencies are correct - -tasks { - withType { dependsOn(generateJsonSchema2Pojo, generateProto) } - withType { dependsOn(generateJsonSchema2Pojo, generateProto) } -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) - exclude("junit-platform.properties") -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/BaseSuspendableCallbackStateMachine.java b/sdk-core/src/main/java/dev/restate/sdk/core/BaseSuspendableCallbackStateMachine.java deleted file mode 100644 index 0c767aae..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/BaseSuspendableCallbackStateMachine.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import java.util.function.Consumer; - -// Implements the base logic for state machines containing suspensable callbacks. -abstract class BaseSuspendableCallbackStateMachine { - - private final CallbackHandle callbackHandle; - private final InputPublisherState inputPublisherState; - - BaseSuspendableCallbackStateMachine() { - this.callbackHandle = new CallbackHandle<>(); - this.inputPublisherState = new InputPublisherState(); - } - - void abort(Throwable cause) { - this.inputPublisherState.notifyClosed(cause); - } - - public void tryFailCallback() { - callbackHandle.consume( - cb -> { - if (inputPublisherState.isSuspended()) { - cb.onSuspend(); - } else if (inputPublisherState.isClosed()) { - cb.onError(inputPublisherState.getCloseCause()); - } - }); - } - - public void consumeCallback(Consumer consumer) { - this.callbackHandle.consume(consumer); - } - - public void consumeCallbackOrElse(Consumer consumer, Runnable elseRunnable) { - this.callbackHandle.consumeOrElse(consumer, elseRunnable); - } - - public void assertCallbackNotSet(String reason) { - if (!this.callbackHandle.isEmpty()) { - throw new IllegalStateException(reason); - } - } - - void setCallback(CB callback) { - if (inputPublisherState.isSuspended()) { - callback.onSuspend(); - } else if (inputPublisherState.isClosed()) { - callback.onError(inputPublisherState.getCloseCause()); - } else { - callbackHandle.set(callback); - } - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/CallbackHandle.java b/sdk-core/src/main/java/dev/restate/sdk/core/CallbackHandle.java deleted file mode 100644 index 6f4fadf8..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/CallbackHandle.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import java.util.function.Consumer; -import org.jspecify.annotations.Nullable; - -/** Handle for callbacks. */ -final class CallbackHandle { - - private @Nullable T cb = null; - - public void set(T t) { - this.cb = t; - } - - public boolean isEmpty() { - return this.cb == null; - } - - public void consume(Consumer consumer) { - if (this.cb != null) { - consumer.accept(pop()); - } - } - - public void consumeOrElse(Consumer consumer, Runnable elseRunnable) { - if (this.cb != null) { - consumer.accept(pop()); - } else { - elseRunnable.run(); - } - } - - private @Nullable T pop() { - T temp = this.cb; - this.cb = null; - return temp; - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/DeferredResults.java b/sdk-core/src/main/java/dev/restate/sdk/core/DeferredResults.java deleted file mode 100644 index 294bc2b4..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/DeferredResults.java +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.Result; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.jspecify.annotations.Nullable; - -abstract class DeferredResults { - - private DeferredResults() {} - - static DeferredInternal single(int entryIndex) { - return new ResolvableSingleDeferred<>(null, entryIndex); - } - - static DeferredInternal completedSingle(int entryIndex, Result result) { - return new ResolvableSingleDeferred<>(result, entryIndex); - } - - static DeferredInternal any(List> any) { - return new AnyDeferred(any); - } - - static DeferredInternal all(List> all) { - return new AllDeferred(all); - } - - interface DeferredInternal extends Deferred { - - @Nullable - @Override - Result toResult(); - - /** - * Look at the implementation of all and any for more details. - * - * @see AllDeferred#tryResolve(int) - * @see AnyDeferred#tryResolve(int) - */ - Stream> unprocessedLeafs(); - } - - interface SingleDeferredInternal extends DeferredInternal { - - int entryIndex(); - } - - private abstract static class BaseDeferred implements DeferredInternal { - - @Nullable private Result readyResult; - - BaseDeferred(@Nullable Result result) { - this.readyResult = result; - } - - @Override - public boolean isCompleted() { - return readyResult != null; - } - - public void resolve(Result result) { - this.readyResult = result; - } - - @Override - @Nullable - public Result toResult() { - return readyResult; - } - } - - static class ResolvableSingleDeferred extends BaseDeferred - implements SingleDeferredInternal { - - private final int entryIndex; - - private ResolvableSingleDeferred(@Nullable Result result, int entryIndex) { - super(result); - this.entryIndex = entryIndex; - } - - @Override - public int entryIndex() { - return entryIndex; - } - - @Override - public Stream> unprocessedLeafs() { - return Stream.of(this); - } - } - - abstract static class CombinatorDeferred extends BaseDeferred { - - // The reason to have these two data structures is to optimize the best case where we have a - // combinator with a large number of single deferred (which can be addressed by entry index), - // but little number of nested combinators (which cannot be addressed by an index, but needs to - // be iterated through). - protected final Map> unresolvedSingles; - protected final Set> unresolvedCombinators; - - CombinatorDeferred( - Map> unresolvedSingles, - Set> unresolvedCombinators) { - super(null); - - this.unresolvedSingles = unresolvedSingles; - this.unresolvedCombinators = unresolvedCombinators; - } - - /** - * This method implements the resolution logic, by trying to solve its leafs and inner - * combinator nodes. - * - *

In case the {@code newResolvedSingle} is unknown/invalid, this method will still try to - * walk through the inner combinator nodes in order to try resolve them. - * - * @return true if it's resolved, that is subsequent calls to {@link #isCompleted()} return - * true. - */ - abstract boolean tryResolve(int newResolvedSingle); - - /** Like {@link #tryResolve(int)}, but iteratively on the provided list. */ - boolean tryResolve(List resolvedSingle) { - boolean resolved = false; - for (int newResolvedSingle : resolvedSingle) { - resolved = tryResolve(newResolvedSingle); - } - return resolved; - } - - @Override - public Stream> unprocessedLeafs() { - return Stream.concat( - this.unresolvedSingles.values().stream(), - this.unresolvedCombinators.stream().flatMap(CombinatorDeferred::unprocessedLeafs)); - } - } - - static class AnyDeferred extends CombinatorDeferred implements Deferred { - - private final IdentityHashMap, Integer> indexMapping; - - private AnyDeferred(List> children) { - super( - children.stream() - .filter(d -> d instanceof SingleDeferredInternal) - .map(d -> (SingleDeferredInternal) d) - .collect(Collectors.toMap(SingleDeferredInternal::entryIndex, Function.identity())), - children.stream() - .filter(d -> d instanceof CombinatorDeferred) - .map(d -> (CombinatorDeferred) d) - .collect(Collectors.toSet())); - - // The index mapping relies on instance hashing - this.indexMapping = new IdentityHashMap<>(); - for (int i = 0; i < children.size(); i++) { - this.indexMapping.put(children.get(i), i); - } - } - - @SuppressWarnings("unchecked") - @Override - boolean tryResolve(int newResolvedSingle) { - if (this.isCompleted()) { - return true; - } - - SingleDeferredInternal resolvedSingle = this.unresolvedSingles.get(newResolvedSingle); - if (resolvedSingle != null) { - // Resolved - this.resolve(Result.success(this.indexMapping.get(resolvedSingle))); - return true; - } - - for (CombinatorDeferred combinator : this.unresolvedCombinators) { - if (combinator.tryResolve(newResolvedSingle)) { - // Resolved - this.resolve(Result.success(this.indexMapping.get(combinator))); - return true; - } - } - - return false; - } - } - - static class AllDeferred extends CombinatorDeferred { - - private AllDeferred(List> children) { - super( - children.stream() - .filter(d -> d instanceof SingleDeferredInternal) - .map(d -> (SingleDeferredInternal) d) - .collect( - Collectors.toMap( - SingleDeferredInternal::entryIndex, - Function.identity(), - (v1, v2) -> v1, - HashMap::new)), - children.stream() - .filter(d -> d instanceof CombinatorDeferred) - .map(d -> (CombinatorDeferred) d) - .collect(Collectors.toCollection(HashSet::new))); - } - - @SuppressWarnings("unchecked") - @Override - boolean tryResolve(int newResolvedSingle) { - if (this.isCompleted()) { - return true; - } - - SingleDeferredInternal resolvedSingle = this.unresolvedSingles.remove(newResolvedSingle); - if (resolvedSingle != null) { - if (!resolvedSingle.toResult().isSuccess()) { - this.resolve((Result) resolvedSingle.toResult()); - return true; - } - } - - Iterator> it = this.unresolvedCombinators.iterator(); - while (it.hasNext()) { - CombinatorDeferred combinator = it.next(); - if (combinator.tryResolve(newResolvedSingle)) { - // Resolved - it.remove(); - - if (!combinator.toResult().isSuccess()) { - this.resolve((Result) combinator.toResult()); - return true; - } - } - } - - if (this.unresolvedSingles.isEmpty() && this.unresolvedCombinators.isEmpty()) { - this.resolve(Result.empty()); - return true; - } - - return false; - } - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java b/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java deleted file mode 100644 index f89d9ac6..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import dev.restate.sdk.common.ComponentType; -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import dev.restate.sdk.core.manifest.Component; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import dev.restate.sdk.core.manifest.Handler; -import java.util.*; -import java.util.stream.Collectors; - -final class DeploymentManifest { - - private final DeploymentManifestSchema manifest; - - public DeploymentManifest( - DeploymentManifestSchema.ProtocolMode protocolMode, - Map components) { - this.manifest = - new DeploymentManifestSchema() - .withMinProtocolVersion(1) - .withMaxProtocolVersion(1) - .withProtocolMode(protocolMode) - .withComponents( - components.values().stream() - .map( - svc -> - new Component() - .withFullyQualifiedComponentName(svc.getFullyQualifiedServiceName()) - .withComponentType(convertComponentType(svc.getComponentType())) - .withHandlers( - svc.getHandlers().stream() - .map( - method -> - new Handler() - .withName(method.getName()) - .withInputSchema(method.getInputSchema()) - .withOutputSchema(method.getOutputSchema())) - .collect(Collectors.toList()))) - .collect(Collectors.toList())); - } - - public DeploymentManifestSchema manifest() { - return this.manifest; - } - - private static Component.ComponentType convertComponentType(ComponentType componentType) { - switch (componentType) { - case WORKFLOW: - case SERVICE: - return Component.ComponentType.SERVICE; - case VIRTUAL_OBJECT: - return Component.ComponentType.VIRTUAL_OBJECT; - } - throw new IllegalStateException(); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/Entries.java b/sdk-core/src/main/java/dev/restate/sdk/core/Entries.java deleted file mode 100644 index 3639377e..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/Entries.java +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import com.google.protobuf.Empty; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.MessageLite; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.generated.service.protocol.Protocol.*; -import dev.restate.sdk.common.syscalls.Result; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import java.util.Collection; -import java.util.function.Function; -import java.util.stream.Collectors; - -final class Entries { - static final String AWAKEABLE_IDENTIFIER_PREFIX = "prom_1"; - - private Entries() {} - - abstract static class JournalEntry { - void checkEntryHeader(E expected, MessageLite actual) throws ProtocolException {} - - abstract void trace(E expected, Span span); - - void updateUserStateStoreWithEntry(E expected, UserStateStore userStateStore) {} - } - - abstract static class CompletableJournalEntry extends JournalEntry { - abstract boolean hasResult(E actual); - - abstract Result parseEntryResult(E actual); - - Result parseCompletionResult(CompletionMessage actual) { - throw ProtocolException.completionDoesNotMatch( - this.getClass().getName(), actual.getResultCase()); - } - - E tryCompleteWithUserStateStorage(E expected, UserStateStore userStateStore) { - return expected; - } - - void updateUserStateStorageWithCompletion( - E expected, CompletionMessage actual, UserStateStore userStateStore) {} - } - - static final class PollInputEntry - extends CompletableJournalEntry { - - static final PollInputEntry INSTANCE = new PollInputEntry(); - - private PollInputEntry() {} - - @Override - public void trace(PollInputStreamEntryMessage expected, Span span) { - span.addEvent("PollInputStream"); - } - - @Override - public boolean hasResult(PollInputStreamEntryMessage actual) { - return actual.getResultCase() != PollInputStreamEntryMessage.ResultCase.RESULT_NOT_SET; - } - - @Override - public Result parseEntryResult(PollInputStreamEntryMessage actual) { - if (actual.getResultCase() == PollInputStreamEntryMessage.ResultCase.VALUE) { - return Result.success(actual.getValue()); - } else if (actual.getResultCase() == PollInputStreamEntryMessage.ResultCase.FAILURE) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } else { - throw new IllegalStateException("PollInputEntry has not been completed."); - } - } - - @Override - public Result parseCompletionResult(CompletionMessage actual) { - if (actual.getResultCase() == CompletionMessage.ResultCase.VALUE) { - return Result.success(actual.getValue()); - } else if (actual.getResultCase() == CompletionMessage.ResultCase.FAILURE) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } - return super.parseCompletionResult(actual); - } - } - - static final class OutputStreamEntry extends JournalEntry { - - static final OutputStreamEntry INSTANCE = new OutputStreamEntry(); - - private OutputStreamEntry() {} - - @Override - public void trace(OutputStreamEntryMessage expected, Span span) { - span.addEvent("OutputStream"); - } - } - - static final class GetStateEntry - extends CompletableJournalEntry { - - static final GetStateEntry INSTANCE = new GetStateEntry(); - - private GetStateEntry() {} - - @Override - void trace(GetStateEntryMessage expected, Span span) { - span.addEvent( - "GetState", Attributes.of(Tracing.RESTATE_STATE_KEY, expected.getKey().toString())); - } - - @Override - public boolean hasResult(GetStateEntryMessage actual) { - return actual.getResultCase() != GetStateEntryMessage.ResultCase.RESULT_NOT_SET; - } - - @Override - void checkEntryHeader(GetStateEntryMessage expected, MessageLite actual) - throws ProtocolException { - if (!(actual instanceof GetStateEntryMessage)) { - throw ProtocolException.entryDoesNotMatch(expected, actual); - } - if (!expected.getKey().equals(((GetStateEntryMessage) actual).getKey())) { - throw ProtocolException.entryDoesNotMatch(expected, actual); - } - } - - @Override - public Result parseEntryResult(GetStateEntryMessage actual) { - if (actual.getResultCase() == GetStateEntryMessage.ResultCase.VALUE) { - return Result.success(actual.getValue()); - } else if (actual.getResultCase() == GetStateEntryMessage.ResultCase.FAILURE) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } else if (actual.getResultCase() == GetStateEntryMessage.ResultCase.EMPTY) { - return Result.empty(); - } else { - throw new IllegalStateException("GetStateEntry has not been completed."); - } - } - - @Override - public Result parseCompletionResult(CompletionMessage actual) { - if (actual.getResultCase() == CompletionMessage.ResultCase.VALUE) { - return Result.success(actual.getValue()); - } else if (actual.getResultCase() == CompletionMessage.ResultCase.EMPTY) { - return Result.empty(); - } else if (actual.getResultCase() == CompletionMessage.ResultCase.FAILURE) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } - return super.parseCompletionResult(actual); - } - - @Override - void updateUserStateStoreWithEntry( - GetStateEntryMessage expected, UserStateStore userStateStore) { - userStateStore.set(expected.getKey(), expected.getValue()); - } - - @Override - GetStateEntryMessage tryCompleteWithUserStateStorage( - GetStateEntryMessage expected, UserStateStore userStateStore) { - UserStateStore.State value = userStateStore.get(expected.getKey()); - if (value instanceof UserStateStore.Value) { - return expected.toBuilder().setValue(((UserStateStore.Value) value).getValue()).build(); - } else if (value instanceof UserStateStore.Empty) { - return expected.toBuilder().setEmpty(Empty.getDefaultInstance()).build(); - } - return expected; - } - - @Override - void updateUserStateStorageWithCompletion( - GetStateEntryMessage expected, CompletionMessage actual, UserStateStore userStateStore) { - if (actual.hasEmpty()) { - userStateStore.clear(expected.getKey()); - } else { - userStateStore.set(expected.getKey(), actual.getValue()); - } - } - } - - static final class GetStateKeysEntry - extends CompletableJournalEntry> { - - static final GetStateKeysEntry INSTANCE = new GetStateKeysEntry(); - - private GetStateKeysEntry() {} - - @Override - void trace(GetStateKeysEntryMessage expected, Span span) { - span.addEvent("GetStateKeys"); - } - - @Override - public boolean hasResult(GetStateKeysEntryMessage actual) { - return actual.getResultCase() != GetStateKeysEntryMessage.ResultCase.RESULT_NOT_SET; - } - - @Override - void checkEntryHeader(GetStateKeysEntryMessage expected, MessageLite actual) - throws ProtocolException { - if (!(actual instanceof GetStateKeysEntryMessage)) { - throw ProtocolException.entryDoesNotMatch(expected, actual); - } - } - - @Override - public Result> parseEntryResult(GetStateKeysEntryMessage actual) { - if (actual.getResultCase() == GetStateKeysEntryMessage.ResultCase.VALUE) { - return Result.success( - actual.getValue().getKeysList().stream() - .map(ByteString::toStringUtf8) - .collect(Collectors.toUnmodifiableList())); - } else if (actual.getResultCase() == GetStateKeysEntryMessage.ResultCase.FAILURE) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } else { - throw new IllegalStateException("GetStateKeysEntryMessage has not been completed."); - } - } - - @Override - public Result> parseCompletionResult(CompletionMessage actual) { - if (actual.getResultCase() == CompletionMessage.ResultCase.VALUE) { - GetStateKeysEntryMessage.StateKeys stateKeys; - try { - stateKeys = GetStateKeysEntryMessage.StateKeys.parseFrom(actual.getValue()); - } catch (InvalidProtocolBufferException e) { - throw new ProtocolException( - "Cannot parse get state keys completion", e, ProtocolException.PROTOCOL_VIOLATION); - } - return Result.success( - stateKeys.getKeysList().stream() - .map(ByteString::toStringUtf8) - .collect(Collectors.toUnmodifiableList())); - } else if (actual.getResultCase() == CompletionMessage.ResultCase.FAILURE) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } - return super.parseCompletionResult(actual); - } - - @Override - GetStateKeysEntryMessage tryCompleteWithUserStateStorage( - GetStateKeysEntryMessage expected, UserStateStore userStateStore) { - if (userStateStore.isComplete()) { - return expected.toBuilder() - .setValue( - GetStateKeysEntryMessage.StateKeys.newBuilder().addAllKeys(userStateStore.keys())) - .build(); - } - return expected; - } - } - - static final class ClearStateEntry extends JournalEntry { - - static final ClearStateEntry INSTANCE = new ClearStateEntry(); - - private ClearStateEntry() {} - - @Override - public void trace(ClearStateEntryMessage expected, Span span) { - span.addEvent( - "ClearState", Attributes.of(Tracing.RESTATE_STATE_KEY, expected.getKey().toString())); - } - - @Override - void checkEntryHeader(ClearStateEntryMessage expected, MessageLite actual) - throws ProtocolException { - Util.assertEntryEquals(expected, actual); - } - - @Override - void updateUserStateStoreWithEntry( - ClearStateEntryMessage expected, UserStateStore userStateStore) { - userStateStore.clear(expected.getKey()); - } - } - - static final class ClearAllStateEntry extends JournalEntry { - - static final ClearAllStateEntry INSTANCE = new ClearAllStateEntry(); - - private ClearAllStateEntry() {} - - @Override - public void trace(ClearAllStateEntryMessage expected, Span span) { - span.addEvent("ClearAllState"); - } - - @Override - void checkEntryHeader(ClearAllStateEntryMessage expected, MessageLite actual) - throws ProtocolException { - Util.assertEntryEquals(expected, actual); - } - - @Override - void updateUserStateStoreWithEntry( - ClearAllStateEntryMessage expected, UserStateStore userStateStore) { - userStateStore.clearAll(); - } - } - - static final class SetStateEntry extends JournalEntry { - - static final SetStateEntry INSTANCE = new SetStateEntry(); - - private SetStateEntry() {} - - @Override - public void trace(SetStateEntryMessage expected, Span span) { - span.addEvent( - "SetState", Attributes.of(Tracing.RESTATE_STATE_KEY, expected.getKey().toString())); - } - - @Override - void checkEntryHeader(SetStateEntryMessage expected, MessageLite actual) - throws ProtocolException { - if (!(actual instanceof SetStateEntryMessage)) { - throw ProtocolException.entryDoesNotMatch(expected, actual); - } - if (!expected.getKey().equals(((SetStateEntryMessage) actual).getKey())) { - throw ProtocolException.entryDoesNotMatch(expected, actual); - } - } - - @Override - void updateUserStateStoreWithEntry( - SetStateEntryMessage expected, UserStateStore userStateStore) { - userStateStore.set(expected.getKey(), expected.getValue()); - } - } - - static final class SleepEntry extends CompletableJournalEntry { - - static final SleepEntry INSTANCE = new SleepEntry(); - - private SleepEntry() {} - - @Override - void trace(SleepEntryMessage expected, Span span) { - span.addEvent( - "Sleep", Attributes.of(Tracing.RESTATE_SLEEP_WAKE_UP_TIME, expected.getWakeUpTime())); - } - - @Override - public boolean hasResult(SleepEntryMessage actual) { - return actual.getResultCase() != Protocol.SleepEntryMessage.ResultCase.RESULT_NOT_SET; - } - - @Override - public Result parseEntryResult(SleepEntryMessage actual) { - if (actual.getResultCase() == SleepEntryMessage.ResultCase.FAILURE) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } else if (actual.getResultCase() == SleepEntryMessage.ResultCase.EMPTY) { - return Result.empty(); - } else { - throw new IllegalStateException("SleepEntry has not been completed."); - } - } - - @Override - public Result parseCompletionResult(CompletionMessage actual) { - if (actual.getResultCase() == CompletionMessage.ResultCase.EMPTY) { - return Result.empty(); - } else if (actual.getResultCase() == CompletionMessage.ResultCase.FAILURE) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } - return super.parseCompletionResult(actual); - } - } - - static final class InvokeEntry extends CompletableJournalEntry { - - private final Function> valueParser; - - InvokeEntry(Function> valueParser) { - this.valueParser = valueParser; - } - - @Override - void trace(InvokeEntryMessage expected, Span span) { - span.addEvent( - "Invoke", - Attributes.of( - Tracing.RESTATE_COORDINATION_CALL_SERVICE, - expected.getServiceName(), - Tracing.RESTATE_COORDINATION_CALL_METHOD, - expected.getMethodName())); - } - - @Override - public boolean hasResult(InvokeEntryMessage actual) { - return actual.getResultCase() != Protocol.InvokeEntryMessage.ResultCase.RESULT_NOT_SET; - } - - @Override - void checkEntryHeader(InvokeEntryMessage expected, MessageLite actual) - throws ProtocolException { - if (!(actual instanceof InvokeEntryMessage)) { - throw ProtocolException.entryDoesNotMatch(expected, actual); - } - InvokeEntryMessage actualInvoke = (InvokeEntryMessage) actual; - - if (!(expected.getServiceName().equals(actualInvoke.getServiceName()) - && expected.getMethodName().equals(actualInvoke.getMethodName()) - && expected.getParameter().equals(actualInvoke.getParameter()))) { - throw ProtocolException.entryDoesNotMatch(expected, actualInvoke); - } - } - - @Override - public Result parseEntryResult(InvokeEntryMessage actual) { - if (actual.hasValue()) { - return valueParser.apply(actual.getValue()); - } - return Result.failure(Util.toRestateException(actual.getFailure())); - } - - @Override - public Result parseCompletionResult(CompletionMessage actual) { - if (actual.hasValue()) { - return valueParser.apply(actual.getValue()); - } - if (actual.hasFailure()) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } - return super.parseCompletionResult(actual); - } - } - - static final class BackgroundInvokeEntry extends JournalEntry { - - static final BackgroundInvokeEntry INSTANCE = new BackgroundInvokeEntry(); - - private BackgroundInvokeEntry() {} - - @Override - public void trace(BackgroundInvokeEntryMessage expected, Span span) { - span.addEvent( - "BackgroundInvoke", - Attributes.of( - Tracing.RESTATE_COORDINATION_CALL_SERVICE, - expected.getServiceName(), - Tracing.RESTATE_COORDINATION_CALL_METHOD, - expected.getMethodName())); - } - - @Override - void checkEntryHeader(BackgroundInvokeEntryMessage expected, MessageLite actual) - throws ProtocolException { - Util.assertEntryEquals(expected, actual); - } - } - - static final class AwakeableEntry - extends CompletableJournalEntry { - static final AwakeableEntry INSTANCE = new AwakeableEntry(); - - private AwakeableEntry() {} - - @Override - void trace(AwakeableEntryMessage expected, Span span) { - span.addEvent("Awakeable"); - } - - @Override - public boolean hasResult(AwakeableEntryMessage actual) { - return actual.getResultCase() != Protocol.AwakeableEntryMessage.ResultCase.RESULT_NOT_SET; - } - - @Override - public Result parseEntryResult(AwakeableEntryMessage actual) { - if (actual.hasValue()) { - return Result.success(actual.getValue()); - } - return Result.failure(Util.toRestateException(actual.getFailure())); - } - - @Override - public Result parseCompletionResult(CompletionMessage actual) { - if (actual.hasValue()) { - return Result.success(actual.getValue()); - } - if (actual.hasFailure()) { - return Result.failure(Util.toRestateException(actual.getFailure())); - } - return super.parseCompletionResult(actual); - } - } - - static final class CompleteAwakeableEntry extends JournalEntry { - - static final CompleteAwakeableEntry INSTANCE = new CompleteAwakeableEntry(); - - private CompleteAwakeableEntry() {} - - @Override - public void trace(CompleteAwakeableEntryMessage expected, Span span) { - span.addEvent("CompleteAwakeable"); - } - - @Override - void checkEntryHeader(CompleteAwakeableEntryMessage expected, MessageLite actual) - throws ProtocolException { - Util.assertEntryEquals(expected, actual); - } - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ExceptionCatchingInvocationInputSubscriber.java b/sdk-core/src/main/java/dev/restate/sdk/core/ExceptionCatchingInvocationInputSubscriber.java deleted file mode 100644 index 99c9598d..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ExceptionCatchingInvocationInputSubscriber.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import java.util.concurrent.Flow; - -class ExceptionCatchingInvocationInputSubscriber - implements InvocationFlow.InvocationInputSubscriber { - - InvocationFlow.InvocationInputSubscriber invocationInputSubscriber; - - public ExceptionCatchingInvocationInputSubscriber( - InvocationFlow.InvocationInputSubscriber invocationInputSubscriber) { - this.invocationInputSubscriber = invocationInputSubscriber; - } - - @Override - public void onSubscribe(Flow.Subscription subscription) { - try { - invocationInputSubscriber.onSubscribe(subscription); - } catch (Throwable throwable) { - invocationInputSubscriber.onError(throwable); - throw throwable; - } - } - - @Override - public void onNext(InvocationFlow.InvocationInput invocationInput) { - try { - invocationInputSubscriber.onNext(invocationInput); - } catch (Throwable throwable) { - invocationInputSubscriber.onError(throwable); - throw throwable; - } - } - - @Override - public void onError(Throwable throwable) { - invocationInputSubscriber.onError(throwable); - } - - @Override - public void onComplete() { - invocationInputSubscriber.onComplete(); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ExecutorSwitchingSyscalls.java b/sdk-core/src/main/java/dev/restate/sdk/core/ExecutorSwitchingSyscalls.java deleted file mode 100644 index c25bbbff..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ExecutorSwitchingSyscalls.java +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.InvocationId; -import dev.restate.sdk.common.Target; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.EnterSideEffectSyscallCallback; -import dev.restate.sdk.common.syscalls.ExitSideEffectSyscallCallback; -import dev.restate.sdk.common.syscalls.SyscallCallback; -import java.time.Duration; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.Executor; -import org.jspecify.annotations.Nullable; - -class ExecutorSwitchingSyscalls implements SyscallsInternal { - - private final SyscallsInternal syscalls; - private final Executor syscallsExecutor; - - ExecutorSwitchingSyscalls(SyscallsInternal syscalls, Executor syscallsExecutor) { - this.syscalls = syscalls; - this.syscallsExecutor = syscallsExecutor; - } - - @Override - public void pollInput(SyscallCallback> callback) { - syscallsExecutor.execute(() -> syscalls.pollInput(callback)); - } - - @Override - public void writeOutput(ByteString value, SyscallCallback callback) { - syscallsExecutor.execute(() -> syscalls.writeOutput(value, callback)); - } - - @Override - public void writeOutput(TerminalException throwable, SyscallCallback callback) { - syscallsExecutor.execute(() -> syscalls.writeOutput(throwable, callback)); - } - - @Override - public void get(String name, SyscallCallback> callback) { - syscallsExecutor.execute(() -> syscalls.get(name, callback)); - } - - @Override - public void getKeys(SyscallCallback>> callback) { - syscallsExecutor.execute(() -> syscalls.getKeys(callback)); - } - - @Override - public void clear(String name, SyscallCallback callback) { - syscallsExecutor.execute(() -> syscalls.clear(name, callback)); - } - - @Override - public void clearAll(SyscallCallback callback) { - syscallsExecutor.execute(() -> syscalls.clearAll(callback)); - } - - @Override - public void set(String name, ByteString value, SyscallCallback callback) { - syscallsExecutor.execute(() -> syscalls.set(name, value, callback)); - } - - @Override - public void sleep(Duration duration, SyscallCallback> callback) { - syscallsExecutor.execute(() -> syscalls.sleep(duration, callback)); - } - - @Override - public void call( - Target target, ByteString parameter, SyscallCallback> callback) { - syscallsExecutor.execute(() -> syscalls.call(target, parameter, callback)); - } - - @Override - public void send( - Target target, - ByteString parameter, - @Nullable Duration delay, - SyscallCallback requestCallback) { - syscallsExecutor.execute(() -> syscalls.send(target, parameter, delay, requestCallback)); - } - - @Override - public void enterSideEffectBlock(EnterSideEffectSyscallCallback callback) { - syscallsExecutor.execute(() -> syscalls.enterSideEffectBlock(callback)); - } - - @Override - public void exitSideEffectBlock(ByteString toWrite, ExitSideEffectSyscallCallback callback) { - syscallsExecutor.execute(() -> syscalls.exitSideEffectBlock(toWrite, callback)); - } - - @Override - public void exitSideEffectBlockWithTerminalException( - TerminalException toWrite, ExitSideEffectSyscallCallback callback) { - syscallsExecutor.execute( - () -> syscalls.exitSideEffectBlockWithTerminalException(toWrite, callback)); - } - - @Override - public void awakeable(SyscallCallback>> callback) { - syscallsExecutor.execute(() -> syscalls.awakeable(callback)); - } - - @Override - public void resolveAwakeable( - String id, ByteString payload, SyscallCallback requestCallback) { - syscallsExecutor.execute(() -> syscalls.resolveAwakeable(id, payload, requestCallback)); - } - - @Override - public void rejectAwakeable(String id, String reason, SyscallCallback requestCallback) { - syscallsExecutor.execute(() -> syscalls.rejectAwakeable(id, reason, requestCallback)); - } - - @Override - public void resolveDeferred(Deferred deferredToResolve, SyscallCallback callback) { - syscallsExecutor.execute(() -> syscalls.resolveDeferred(deferredToResolve, callback)); - } - - @Override - public String getFullyQualifiedMethodName() { - // We can read this from another thread - return syscalls.getFullyQualifiedMethodName(); - } - - @Override - public InvocationState getInvocationState() { - // We can read this from another thread - return syscalls.getInvocationState(); - } - - @Override - public InvocationId invocationId() { - // This is immutable once set - return syscalls.invocationId(); - } - - @Override - public String objectKey() { - // This is immutable once set - return syscalls.objectKey(); - } - - @Override - public boolean isInsideSideEffect() { - // We can read this from another thread - return syscalls.isInsideSideEffect(); - } - - @Override - public void close() { - syscallsExecutor.execute(syscalls::close); - } - - @Override - public void fail(Throwable cause) { - syscallsExecutor.execute(() -> syscalls.fail(cause)); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/IncomingEntriesStateMachine.java b/sdk-core/src/main/java/dev/restate/sdk/core/IncomingEntriesStateMachine.java deleted file mode 100644 index 29ace7ad..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/IncomingEntriesStateMachine.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.MessageLite; -import java.util.ArrayDeque; -import java.util.Queue; - -class IncomingEntriesStateMachine - extends BaseSuspendableCallbackStateMachine { - - interface OnEntryCallback extends SuspendableCallback { - void onEntry(MessageLite msg); - } - - private final Queue unprocessedMessages; - - IncomingEntriesStateMachine() { - this.unprocessedMessages = new ArrayDeque<>(); - } - - void offer(MessageLite msg) { - Util.assertIsEntry(msg); - this.consumeCallbackOrElse(cb -> cb.onEntry(msg), () -> this.unprocessedMessages.offer(msg)); - } - - void read(OnEntryCallback msgCallback) { - this.assertCallbackNotSet("Two concurrent reads were requested."); - - MessageLite popped = this.unprocessedMessages.poll(); - if (popped != null) { - msgCallback.onEntry(popped); - } else { - this.setCallback(msgCallback); - } - } - - boolean isEmpty() { - return this.unprocessedMessages.isEmpty(); - } - - @Override - void abort(Throwable cause) { - super.abort(cause); - // We can't do anything else if the input stream is closed, so we just fail the callback, if any - this.tryFailCallback(); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/InputPublisherState.java b/sdk-core/src/main/java/dev/restate/sdk/core/InputPublisherState.java deleted file mode 100644 index e71f6650..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/InputPublisherState.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import dev.restate.sdk.common.AbortedExecutionException; -import org.jspecify.annotations.Nullable; - -class InputPublisherState { - - private @Nullable Throwable closeCause = null; - - void notifyClosed(Throwable cause) { - closeCause = cause; - } - - boolean isSuspended() { - return this.closeCause == AbortedExecutionException.INSTANCE; - } - - boolean isClosed() { - return this.closeCause != null; - } - - public Throwable getCloseCause() { - return closeCause; - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationFlow.java b/sdk-core/src/main/java/dev/restate/sdk/core/InvocationFlow.java deleted file mode 100644 index 5c79f314..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationFlow.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.MessageLite; -import java.util.concurrent.Flow; - -public interface InvocationFlow { - - interface InvocationInput { - MessageHeader header(); - - MessageLite message(); - - static InvocationInput of(MessageHeader header, MessageLite message) { - return new InvocationInput() { - @Override - public MessageHeader header() { - return header; - } - - @Override - public MessageLite message() { - return message; - } - - @Override - public String toString() { - return header.toString() + " " + message.toString(); - } - }; - } - } - - interface InvocationInputPublisher extends Flow.Publisher {} - - interface InvocationOutputPublisher extends Flow.Publisher {} - - interface InvocationInputSubscriber extends Flow.Subscriber {} - - interface InvocationOutputSubscriber extends Flow.Subscriber {} - - interface InvocationProcessor - extends Flow.Processor, - InvocationInputSubscriber, - InvocationOutputPublisher {} -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationIdImpl.java b/sdk-core/src/main/java/dev/restate/sdk/core/InvocationIdImpl.java deleted file mode 100644 index ac66c5b8..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationIdImpl.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import dev.restate.sdk.common.InvocationId; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Objects; - -final class InvocationIdImpl implements InvocationId { - - private final String id; - private Long seed; - - InvocationIdImpl(String debugId) { - this.id = debugId; - this.seed = null; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - InvocationIdImpl that = (InvocationIdImpl) o; - return Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - - @Override - public long toRandomSeed() { - if (seed == null) { - // Hash the seed to SHA-256 to increase entropy - MessageDigest md; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - byte[] digest = md.digest(id.getBytes(StandardCharsets.UTF_8)); - - // Generate the long - long n = 0; - n |= ((long) (digest[7] & 0xFF) << Byte.SIZE * 7); - n |= ((long) (digest[6] & 0xFF) << Byte.SIZE * 6); - n |= ((long) (digest[5] & 0xFF) << Byte.SIZE * 5); - n |= ((long) (digest[4] & 0xFF) << Byte.SIZE * 4); - n |= ((long) (digest[3] & 0xFF) << Byte.SIZE * 3); - n |= ((digest[2] & 0xFF) << Byte.SIZE * 2); - n |= ((digest[1] & 0xFF) << Byte.SIZE); - n |= (digest[0] & 0xFF); - seed = n; - } - return seed; - } - - @Override - public String toString() { - return id; - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationState.java b/sdk-core/src/main/java/dev/restate/sdk/core/InvocationState.java deleted file mode 100644 index 387d30fd..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationState.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -public enum InvocationState { - WAITING_START, - REPLAYING, - PROCESSING, - CLOSED; -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationStateMachine.java b/sdk-core/src/main/java/dev/restate/sdk/core/InvocationStateMachine.java deleted file mode 100644 index fa0fe90b..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationStateMachine.java +++ /dev/null @@ -1,731 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import com.google.protobuf.MessageLite; -import dev.restate.generated.sdk.java.Java; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.common.AbortedExecutionException; -import dev.restate.sdk.common.InvocationId; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.common.syscalls.*; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import java.util.*; -import java.util.concurrent.Flow; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -class InvocationStateMachine implements InvocationFlow.InvocationProcessor { - - private static final Logger LOG = LogManager.getLogger(InvocationStateMachine.class); - - private final String serviceName; - private final String fullyQualifiedMethodName; - private final Span span; - private final Consumer transitionStateObserver; - - private volatile InvocationState invocationState = InvocationState.WAITING_START; - - // Used for the side effect guard - private volatile boolean insideSideEffect = false; - - // Obtained after WAITING_START - private ByteString id; - private InvocationIdImpl invocationId; - private String key; - private int entriesToReplay; - private UserStateStore userStateStore; - - // Index tracking progress in the journal - private int currentJournalIndex; - - // Buffering of messages and completions - private final IncomingEntriesStateMachine incomingEntriesStateMachine; - private final SideEffectAckStateMachine sideEffectAckStateMachine; - private final ReadyResultStateMachine readyResultStateMachine; - - // Flow sub/pub - private Flow.Subscriber outputSubscriber; - private Flow.Subscription inputSubscription; - private final CallbackHandle> afterStartCallback; - - InvocationStateMachine( - String serviceName, - String fullyQualifiedMethodName, - Span span, - Consumer transitionStateObserver) { - this.serviceName = serviceName; - this.fullyQualifiedMethodName = fullyQualifiedMethodName; - this.span = span; - this.transitionStateObserver = transitionStateObserver; - - this.incomingEntriesStateMachine = new IncomingEntriesStateMachine(); - this.readyResultStateMachine = new ReadyResultStateMachine(); - this.sideEffectAckStateMachine = new SideEffectAckStateMachine(); - - this.afterStartCallback = new CallbackHandle<>(); - } - - // --- Getters - - public String getServiceName() { - return serviceName; - } - - public ByteString id() { - return id; - } - - public InvocationId invocationId() { - return this.invocationId; - } - - public String objectKey() { - return key; - } - - public InvocationState getInvocationState() { - return this.invocationState; - } - - public boolean isInsideSideEffect() { - return this.insideSideEffect; - } - - public String getFullyQualifiedMethodName() { - return this.fullyQualifiedMethodName; - } - - // --- Output Publisher impl - - @Override - public void subscribe(Flow.Subscriber subscriber) { - this.outputSubscriber = subscriber; - this.outputSubscriber.onSubscribe( - new Flow.Subscription() { - @Override - public void request(long l) {} - - @Override - public void cancel() { - end(); - } - }); - } - - // --- Input Subscriber impl - - @Override - public void onSubscribe(Flow.Subscription subscription) { - this.inputSubscription = subscription; - } - - @Override - public void onNext(InvocationFlow.InvocationInput invocationInput) { - MessageLite msg = invocationInput.message(); - LOG.trace("Received input message {} {}", msg.getClass(), msg); - if (this.invocationState == InvocationState.WAITING_START) { - MessageHeader.checkProtocolVersion(invocationInput.header()); - this.onStart(msg); - } else if (msg instanceof Protocol.CompletionMessage) { - // We check the instance rather than the state, because the user code might still be - // replaying, but the network layer is already past it and is receiving completions from the - // runtime. - this.readyResultStateMachine.offerCompletion((Protocol.CompletionMessage) msg); - } else if (msg instanceof Protocol.EntryAckMessage) { - this.sideEffectAckStateMachine.tryHandleSideEffectAck( - ((Protocol.EntryAckMessage) msg).getEntryIndex()); - } else { - this.incomingEntriesStateMachine.offer(msg); - } - } - - @Override - public void onError(Throwable throwable) { - LOG.trace("Got failure from input publisher", throwable); - this.fail(throwable); - } - - @Override - public void onComplete() { - LOG.trace("Input publisher closed"); - this.readyResultStateMachine.abort(AbortedExecutionException.INSTANCE); - this.sideEffectAckStateMachine.abort(AbortedExecutionException.INSTANCE); - } - - // --- Init routine to wait for the start message - - void start(Consumer afterStartCallback) { - this.afterStartCallback.set(afterStartCallback); - this.inputSubscription.request(1); - } - - void onStart(MessageLite msg) { - if (!(msg instanceof Protocol.StartMessage)) { - this.fail(ProtocolException.unexpectedMessage(Protocol.StartMessage.class, msg)); - return; - } - - // Unpack the StartMessage - Protocol.StartMessage startMessage = (Protocol.StartMessage) msg; - this.id = startMessage.getId(); - this.invocationId = new InvocationIdImpl(startMessage.getDebugId()); - this.key = startMessage.getKey(); - this.entriesToReplay = startMessage.getKnownEntries(); - - // Set up the state cache - this.userStateStore = - new UserStateStore( - startMessage.getPartialState(), - startMessage.getStateMapList().stream() - .collect( - Collectors.toMap( - Protocol.StartMessage.StateEntry::getKey, - Protocol.StartMessage.StateEntry::getValue))); - - if (this.span.isRecording()) { - span.addEvent( - "Start", Attributes.of(Tracing.RESTATE_INVOCATION_ID, startMessage.getDebugId())); - } - - // Execute state transition - this.transitionState(InvocationState.REPLAYING); - if (this.entriesToReplay == 0) { - this.transitionState(InvocationState.PROCESSING); - } - - this.inputSubscription.request(Long.MAX_VALUE); - - // Now execute the callback after start - this.afterStartCallback.consume(cb -> cb.accept(this.invocationId)); - } - - // --- Close state machine - - void end() { - LOG.info("End invocation"); - this.closeWithMessage(Protocol.EndMessage.getDefaultInstance(), ProtocolException.CLOSED); - } - - void suspend(Collection suspensionIndexes) { - assert !suspensionIndexes.isEmpty() - : "Suspension indexes MUST be a non-empty collection, per protocol specification"; - LOG.info("Suspend invocation"); - this.closeWithMessage( - Protocol.SuspensionMessage.newBuilder().addAllEntryIndexes(suspensionIndexes).build(), - ProtocolException.CLOSED); - } - - void fail(Throwable cause) { - LOG.warn("Invocation failed", cause); - Protocol.ErrorMessage msg; - if (cause instanceof ProtocolException) { - msg = ((ProtocolException) cause).toErrorMessage(); - } else { - msg = - Protocol.ErrorMessage.newBuilder() - .setCode(TerminalException.Code.UNKNOWN.value()) - .setMessage(cause.toString()) - .build(); - } - this.closeWithMessage(msg, cause); - } - - private void closeWithMessage(MessageLite closeMessage, Throwable cause) { - if (this.invocationState != InvocationState.CLOSED) { - this.transitionState(InvocationState.CLOSED); - - // Cancel inputSubscription and complete outputSubscriber - if (inputSubscription != null) { - this.inputSubscription.cancel(); - } - if (this.outputSubscriber != null) { - this.outputSubscriber.onNext(closeMessage); - this.outputSubscriber.onComplete(); - this.outputSubscriber = null; - } - - // Unblock any eventual waiting callbacks - this.readyResultStateMachine.abort(cause); - this.sideEffectAckStateMachine.abort(cause); - this.incomingEntriesStateMachine.abort(cause); - this.span.end(); - } - } - - // --- Methods to implement Syscalls - - @SuppressWarnings("unchecked") - void processCompletableJournalEntry( - E expectedEntryMessage, - Entries.CompletableJournalEntry journalEntry, - SyscallCallback> callback) { - checkInsideSideEffectGuard(); - if (this.invocationState == InvocationState.CLOSED) { - callback.onCancel(AbortedExecutionException.INSTANCE); - } else if (this.invocationState == InvocationState.REPLAYING) { - // Retrieve the entry - this.readEntry( - (entryIndex, actualEntryMessage) -> { - journalEntry.checkEntryHeader(expectedEntryMessage, actualEntryMessage); - - if (journalEntry.hasResult((E) actualEntryMessage)) { - // Entry is already completed - journalEntry.updateUserStateStoreWithEntry( - (E) actualEntryMessage, this.userStateStore); - Result readyResultInternal = journalEntry.parseEntryResult((E) actualEntryMessage); - callback.onSuccess(DeferredResults.completedSingle(entryIndex, readyResultInternal)); - } else { - // Entry is not completed yet - this.readyResultStateMachine.offerCompletionParser( - entryIndex, - completionMessage -> { - journalEntry.updateUserStateStorageWithCompletion( - (E) actualEntryMessage, completionMessage, this.userStateStore); - return journalEntry.parseCompletionResult(completionMessage); - }); - callback.onSuccess(DeferredResults.single(entryIndex)); - } - }, - callback::onCancel); - } else if (this.invocationState == InvocationState.PROCESSING) { - // Try complete with local storage - E entryToWrite = - journalEntry.tryCompleteWithUserStateStorage(expectedEntryMessage, userStateStore); - - if (span.isRecording()) { - journalEntry.trace(entryToWrite, span); - } - - // Retrieve the index - int entryIndex = this.currentJournalIndex; - - // Write out the input entry - this.writeEntry(entryToWrite); - - if (journalEntry.hasResult(entryToWrite)) { - // Complete it with the result, as we already have it - callback.onSuccess( - DeferredResults.completedSingle( - entryIndex, journalEntry.parseEntryResult(entryToWrite))); - } else { - // Register the completion parser - this.readyResultStateMachine.offerCompletionParser( - entryIndex, - completionMessage -> { - journalEntry.updateUserStateStorageWithCompletion( - entryToWrite, completionMessage, this.userStateStore); - return journalEntry.parseCompletionResult(completionMessage); - }); - - // Call the onSuccess - callback.onSuccess(DeferredResults.single(entryIndex)); - } - } else { - throw new IllegalStateException( - "This method was invoked when the state machine is not ready to process user code. This is probably an SDK bug"); - } - } - - @SuppressWarnings("unchecked") - void processJournalEntry( - E expectedEntryMessage, - Entries.JournalEntry journalEntry, - SyscallCallback callback) { - checkInsideSideEffectGuard(); - if (this.invocationState == InvocationState.CLOSED) { - callback.onCancel(AbortedExecutionException.INSTANCE); - } else if (this.invocationState == InvocationState.REPLAYING) { - // Retrieve the entry - this.readEntry( - (entryIndex, actualEntryMessage) -> { - journalEntry.checkEntryHeader(expectedEntryMessage, actualEntryMessage); - journalEntry.updateUserStateStoreWithEntry((E) actualEntryMessage, this.userStateStore); - callback.onSuccess(null); - }, - callback::onCancel); - } else if (this.invocationState == InvocationState.PROCESSING) { - if (span.isRecording()) { - journalEntry.trace(expectedEntryMessage, span); - } - - // Write new entry - this.writeEntry(expectedEntryMessage); - - // Update local storage - journalEntry.updateUserStateStoreWithEntry(expectedEntryMessage, this.userStateStore); - - // Invoke the ok callback - callback.onSuccess(null); - } else { - throw new IllegalStateException( - "This method was invoked when the state machine is not ready to process user code. This is probably an SDK bug"); - } - } - - void enterSideEffectBlock(EnterSideEffectSyscallCallback callback) { - checkInsideSideEffectGuard(); - if (this.invocationState == InvocationState.CLOSED) { - callback.onCancel(AbortedExecutionException.INSTANCE); - } else if (this.invocationState == InvocationState.REPLAYING) { - // Retrieve the entry - this.readEntry( - (entryIndex, msg) -> { - Util.assertEntryClass(Java.SideEffectEntryMessage.class, msg); - - // We have a result already, complete the callback - completeSideEffectCallbackWithEntry((Java.SideEffectEntryMessage) msg, callback); - }, - callback::onCancel); - } else if (this.invocationState == InvocationState.PROCESSING) { - insideSideEffect = true; - if (span.isRecording()) { - span.addEvent("Enter SideEffect"); - } - callback.onNotExecuted(); - } else { - throw new IllegalStateException( - "This method was invoked when the state machine is not ready to process user code. This is probably an SDK bug"); - } - } - - void exitSideEffectBlock( - Java.SideEffectEntryMessage sideEffectEntry, ExitSideEffectSyscallCallback callback) { - this.insideSideEffect = false; - if (this.invocationState == InvocationState.CLOSED) { - callback.onCancel(AbortedExecutionException.INSTANCE); - } else if (this.invocationState == InvocationState.REPLAYING) { - throw new IllegalStateException( - "exitSideEffect has been invoked when the state machine is in replaying mode. " - + "This is probably an SDK bug and might be caused by a missing enterSideEffectBlock invocation before exitSideEffectBlock."); - } else if (this.invocationState == InvocationState.PROCESSING) { - if (span.isRecording()) { - span.addEvent("Exit SideEffect"); - } - - // Write new entry - this.sideEffectAckStateMachine.registerExecutedSideEffect(this.currentJournalIndex); - this.writeEntry(sideEffectEntry); - - // Wait for entry to be acked - this.sideEffectAckStateMachine.waitLastSideEffectAck( - new SideEffectAckStateMachine.SideEffectAckCallback() { - @Override - public void onLastSideEffectAck() { - completeSideEffectCallbackWithEntry(sideEffectEntry, callback); - } - - @Override - public void onSuspend() { - suspend(List.of(sideEffectAckStateMachine.getLastExecutedSideEffect())); - callback.onCancel(AbortedExecutionException.INSTANCE); - } - - @Override - public void onError(Throwable e) { - callback.onCancel(e); - } - }); - } else { - throw new IllegalStateException( - "This method was invoked when the state machine is not ready to process user code. This is probably an SDK bug"); - } - } - - void completeSideEffectCallbackWithEntry( - Java.SideEffectEntryMessage sideEffectEntry, ExitSideEffectSyscallCallback callback) { - if (sideEffectEntry.hasFailure()) { - callback.onFailure(Util.toRestateException(sideEffectEntry.getFailure())); - } else { - callback.onSuccess(sideEffectEntry.getValue()); - } - } - - // --- Deferred - - void resolveDeferred(Deferred deferredToResolve, SyscallCallback callback) { - if (deferredToResolve.isCompleted()) { - callback.onSuccess(null); - return; - } - - if (deferredToResolve instanceof DeferredResults.ResolvableSingleDeferred) { - this.resolveSingleDeferred( - (DeferredResults.ResolvableSingleDeferred) deferredToResolve, callback); - return; - } - - if (deferredToResolve instanceof DeferredResults.CombinatorDeferred) { - this.resolveCombinatorDeferred( - (DeferredResults.CombinatorDeferred) deferredToResolve, callback); - return; - } - - throw new IllegalArgumentException("Unexpected deferred class " + deferredToResolve.getClass()); - } - - void resolveSingleDeferred( - DeferredResults.ResolvableSingleDeferred deferred, SyscallCallback callback) { - this.readyResultStateMachine.onNewReadyResult( - new ReadyResultStateMachine.OnNewReadyResultCallback() { - @SuppressWarnings("unchecked") - @Override - public boolean onNewResult(Map> resultMap) { - Result resolved = (Result) resultMap.remove(deferred.entryIndex()); - if (resolved != null) { - deferred.resolve(resolved); - callback.onSuccess(null); - return true; - } - return false; - } - - @Override - public void onSuspend() { - suspend(List.of(deferred.entryIndex())); - callback.onCancel(AbortedExecutionException.INSTANCE); - } - - @Override - public void onError(Throwable e) { - callback.onCancel(e); - } - }); - } - - /** - * This method implements the algorithm to resolve deferred combinator trees, where inner nodes of - * the tree are ANY or ALL combinators, and leafs are {@link - * DeferredResults.ResolvableSingleDeferred}, created as result of completable syscalls. - * - *

The idea of the algorithm is the following: {@code rootDeferred} is the root of this tree, - * and has internal state that can be mutated through {@link - * DeferredResults.CombinatorDeferred#tryResolve(int)} to flag the tree as resolved. Every time a - * new leaf is resolved through {@link DeferredResults.ResolvableSingleDeferred#resolve(Result)}, - * we try to resolve the tree again. We start by checking if we have enough resolved leafs in the - * combinator tree to resolve it. If not, we register a callback to the {@link - * ReadyResultStateMachine} to wait on future completions. As soon as the tree is resolved, we - * record in the journal the order of the leafs we've seen so far, and we finish by calling the - * {@code callback}, giving back control to user code. - * - *

An important property of this algorithm is that we don't write multiple {@link - * Java.CombinatorAwaitableEntryMessage} per combinator nodes composing the tree, but we write one - * of them for the whole tree. Moreover, we write only when we resolve the combinator tree, and - * not beforehand when the user creates the combinator tree. The main reason for this property is - * that the Restate protocol doesn't allow the SDK to mutate Journal Entries after they're sent to - * the runtime, and the index of entries is enforced by their send order, meaning you cannot send - * entry 2 and then entry 1. The consequence of this property is that any algorithm recording - * combinator nodes one-by-one would require non-trivial replay logic, in order to handle the - * resolution order of the combinator nodes, and partially resolved trees (e.g. in case a - * suspension happens while we have recorded only a part of the combinator nodes). - * - *

There are some special cases: - * - *

    - *
  • In case of replay, we don't need to wait for any leaf to be resolved, because we write - * the combinator journal entry only when there is a subset of resolved leafs which - * completes the combinator tree. Moreover, the leaf journal entries precede the combinator - * entry because they are created first. - *
  • In case there are no {@link DeferredResults.SingleDeferredInternal - * SingleDeferredResultInternals}, it means every leaf has been resolved beforehand. In this - * case, we must be able to flag this combinator tree as resolved as well. - *
- */ - private void resolveCombinatorDeferred( - DeferredResults.CombinatorDeferred rootDeferred, SyscallCallback callback) { - // Calling .await() on a combinator deferred within a side effect is not allowed - // as resolving it creates or read a journal entry. - checkInsideSideEffectGuard(); - if (Objects.equals(this.invocationState, InvocationState.REPLAYING)) { - // Retrieve the CombinatorAwaitableEntryMessage - this.readEntry( - (entryIndex, actualMsg) -> { - Util.assertEntryClass(Java.CombinatorAwaitableEntryMessage.class, actualMsg); - - if (!rootDeferred.tryResolve( - ((Java.CombinatorAwaitableEntryMessage) actualMsg).getEntryIndexList())) { - throw new IllegalStateException("Combinator message cannot be resolved."); - } - callback.onSuccess(null); - }, - callback::onCancel); - } else if (this.invocationState == InvocationState.PROCESSING) { - // Create map of singles to resolve - Map> resolvableSingles = new HashMap<>(); - - Set> unprocessedLeafs = - rootDeferred.unprocessedLeafs().collect(Collectors.toSet()); - - // If there are no leafs, it means the combinator must be resolvable - if (unprocessedLeafs.isEmpty()) { - // We don't need to provide a valid entry index, - // we just need to walk through the tree and mark all the combinators as completed. - if (!rootDeferred.tryResolve(-1)) { - throw new IllegalStateException( - "Combinator cannot be resolved, but every children have been resolved already. " - + "This is a symptom of an SDK bug, please contact the developers."); - } - - writeCombinatorEntry(Collections.emptyList()); - callback.onSuccess(null); - return; - } - - List resolvedOrder = new ArrayList<>(); - - // Walk the tree and populate the resolvable singles, and keep the already known ready results - for (DeferredResults.SingleDeferredInternal singleDeferred : unprocessedLeafs) { - int entryIndex = singleDeferred.entryIndex(); - if (singleDeferred.isCompleted()) { - resolvedOrder.add(entryIndex); - - // Try to resolve the combinator now - if (rootDeferred.tryResolve(entryIndex)) { - writeCombinatorEntry(resolvedOrder); - callback.onSuccess(null); - return; - } - } else { - // If not completed, then it's a ResolvableSingleDeferredResult - resolvableSingles.put( - entryIndex, (DeferredResults.ResolvableSingleDeferred) singleDeferred); - } - } - - // Not completed yet, we need to wait on the ReadyResultPublisher - this.readyResultStateMachine.onNewReadyResult( - new ReadyResultStateMachine.OnNewReadyResultCallback() { - @SuppressWarnings({"unchecked", "rawtypes"}) - @Override - public boolean onNewResult(Map> resultMap) { - Iterator>> it = - resolvableSingles.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry> entry = it.next(); - int entryIndex = entry.getKey(); - - Result result = resultMap.remove(entryIndex); - if (result != null) { - resolvedOrder.add(entryIndex); - entry.getValue().resolve((Result) result); - it.remove(); - - // Try to resolve the combinator now - if (rootDeferred.tryResolve(entryIndex)) { - writeCombinatorEntry(resolvedOrder); - callback.onSuccess(null); - return true; - } - } - } - - return false; - } - - @Override - public void onSuspend() { - suspend(resolvableSingles.keySet()); - callback.onCancel(AbortedExecutionException.INSTANCE); - } - - @Override - public void onError(Throwable e) { - callback.onCancel(e); - } - }); - } else { - throw new IllegalStateException( - "This method was invoked when the state machine is not ready to process user code. This is probably an SDK bug"); - } - } - - private void writeCombinatorEntry(List resolvedList) { - // Create and write the entry - Java.CombinatorAwaitableEntryMessage entry = - Java.CombinatorAwaitableEntryMessage.newBuilder().addAllEntryIndex(resolvedList).build(); - span.addEvent("Combinator"); - writeEntry(entry); - } - - // --- Internal callback - - private void transitionState(InvocationState newInvocationState) { - if (this.invocationState == InvocationState.CLOSED) { - // Cannot move out of the closed state - return; - } - LOG.debug("Transitioning {} to {}", this, newInvocationState); - this.invocationState = newInvocationState; - this.transitionStateObserver.accept(newInvocationState); - } - - private void incrementCurrentIndex() { - this.currentJournalIndex++; - - if (currentJournalIndex >= entriesToReplay - && this.invocationState == InvocationState.REPLAYING) { - if (!this.incomingEntriesStateMachine.isEmpty()) { - throw new IllegalStateException("Entries queue should be empty at this point"); - } - this.transitionState(InvocationState.PROCESSING); - } - } - - private void checkInsideSideEffectGuard() { - if (this.insideSideEffect) { - throw ProtocolException.invalidSideEffectCall(); - } - } - - void readEntry(BiConsumer msgCallback, Consumer errorCallback) { - this.incomingEntriesStateMachine.read( - new IncomingEntriesStateMachine.OnEntryCallback() { - @Override - public void onEntry(MessageLite msg) { - incrementCurrentIndex(); - msgCallback.accept(currentJournalIndex - 1, msg); - } - - @Override - public void onSuspend() { - // This is not expected to happen, so we treat this case as closed - errorCallback.accept(ProtocolException.CLOSED); - } - - @Override - public void onError(Throwable e) { - errorCallback.accept(e); - } - }); - } - - private void writeEntry(MessageLite message) { - LOG.trace("Writing to output message {} {}", message.getClass(), message); - Objects.requireNonNull(this.outputSubscriber).onNext(message); - this.incrementCurrentIndex(); - } - - @Override - public String toString() { - return "InvocationStateMachine{" - + "serviceName='" - + serviceName - + '\'' - + ", invocationState=" - + invocationState - + ", id=" - + invocationId - + '}'; - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/MessageHeader.java b/sdk-core/src/main/java/dev/restate/sdk/core/MessageHeader.java deleted file mode 100644 index 96e00bf4..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/MessageHeader.java +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.MessageLite; -import dev.restate.generated.sdk.java.Java; -import dev.restate.generated.service.protocol.Protocol; - -public class MessageHeader { - - static final short SUPPORTED_PROTOCOL_VERSION = 1; - - static final short VERSION_MASK = 0x03FF; - static final short DONE_FLAG = 0x0001; - static final int REQUIRES_ACK_FLAG = 0x8000; - - private final MessageType type; - private final int flags; - private final int length; - - public MessageHeader(MessageType type, int flags, int length) { - this.type = type; - this.flags = flags; - this.length = length; - } - - public MessageType getType() { - return type; - } - - public int getLength() { - return length; - } - - public long encode() { - long res = 0L; - res |= ((long) type.encode() << 48); - res |= ((long) flags << 32); - res |= length; - return res; - } - - public static MessageHeader parse(long encoded) throws ProtocolException { - var ty_code = (short) (encoded >> 48); - var flags = (short) (encoded >> 32); - var len = (int) encoded; - - return new MessageHeader(MessageType.decode(ty_code), flags, len); - } - - public static MessageHeader fromMessage(MessageLite msg) { - if (msg instanceof Protocol.SuspensionMessage) { - return new MessageHeader(MessageType.SuspensionMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.ErrorMessage) { - return new MessageHeader(MessageType.ErrorMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.EndMessage) { - return new MessageHeader(MessageType.EndMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.EntryAckMessage) { - return new MessageHeader(MessageType.EntryAckMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.PollInputStreamEntryMessage) { - return new MessageHeader( - MessageType.PollInputStreamEntryMessage, - ((Protocol.PollInputStreamEntryMessage) msg).getResultCase() - != Protocol.PollInputStreamEntryMessage.ResultCase.RESULT_NOT_SET - ? DONE_FLAG - : 0, - msg.getSerializedSize()); - } else if (msg instanceof Protocol.OutputStreamEntryMessage) { - return new MessageHeader(MessageType.OutputStreamEntryMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.GetStateEntryMessage) { - return new MessageHeader( - MessageType.GetStateEntryMessage, - ((Protocol.GetStateEntryMessage) msg).getResultCase() - != Protocol.GetStateEntryMessage.ResultCase.RESULT_NOT_SET - ? DONE_FLAG - : 0, - msg.getSerializedSize()); - } else if (msg instanceof Protocol.SetStateEntryMessage) { - return new MessageHeader(MessageType.SetStateEntryMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.ClearStateEntryMessage) { - return new MessageHeader(MessageType.ClearStateEntryMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.ClearAllStateEntryMessage) { - return new MessageHeader(MessageType.ClearAllStateEntryMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.GetStateKeysEntryMessage) { - return new MessageHeader( - MessageType.GetStateKeysEntryMessage, - ((Protocol.GetStateKeysEntryMessage) msg).getResultCase() - != Protocol.GetStateKeysEntryMessage.ResultCase.RESULT_NOT_SET - ? DONE_FLAG - : 0, - msg.getSerializedSize()); - } else if (msg instanceof Protocol.SleepEntryMessage) { - return new MessageHeader( - MessageType.SleepEntryMessage, - ((Protocol.SleepEntryMessage) msg).getResultCase() - != Protocol.SleepEntryMessage.ResultCase.RESULT_NOT_SET - ? DONE_FLAG - : 0, - msg.getSerializedSize()); - } else if (msg instanceof Protocol.InvokeEntryMessage) { - return new MessageHeader( - MessageType.InvokeEntryMessage, - ((Protocol.InvokeEntryMessage) msg).getResultCase() - != Protocol.InvokeEntryMessage.ResultCase.RESULT_NOT_SET - ? DONE_FLAG - : 0, - msg.getSerializedSize()); - } else if (msg instanceof Protocol.BackgroundInvokeEntryMessage) { - return new MessageHeader( - MessageType.BackgroundInvokeEntryMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Protocol.AwakeableEntryMessage) { - return new MessageHeader( - MessageType.AwakeableEntryMessage, - ((Protocol.AwakeableEntryMessage) msg).getResultCase() - != Protocol.AwakeableEntryMessage.ResultCase.RESULT_NOT_SET - ? DONE_FLAG - : 0, - msg.getSerializedSize()); - } else if (msg instanceof Protocol.CompleteAwakeableEntryMessage) { - return new MessageHeader( - MessageType.CompleteAwakeableEntryMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Java.CombinatorAwaitableEntryMessage) { - return new MessageHeader( - MessageType.CombinatorAwaitableEntryMessage, 0, msg.getSerializedSize()); - } else if (msg instanceof Java.SideEffectEntryMessage) { - return new MessageHeader( - MessageType.SideEffectEntryMessage, REQUIRES_ACK_FLAG, msg.getSerializedSize()); - } else if (msg instanceof Protocol.CompletionMessage) { - throw new IllegalArgumentException("SDK should never send a CompletionMessage"); - } - throw new IllegalStateException(); - } - - public static void checkProtocolVersion(MessageHeader header) { - if (header.type != MessageType.StartMessage) { - throw new IllegalStateException("Expected StartMessage, got " + header.type); - } - - short version = (short) (header.flags & VERSION_MASK); - if (version != SUPPORTED_PROTOCOL_VERSION) { - throw new IllegalStateException( - "Unsupported protocol version " - + version - + ", only version " - + SUPPORTED_PROTOCOL_VERSION - + " is supported"); - } - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/MessageType.java b/sdk-core/src/main/java/dev/restate/sdk/core/MessageType.java deleted file mode 100644 index c1efb0ba..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/MessageType.java +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.MessageLite; -import com.google.protobuf.Parser; -import dev.restate.generated.sdk.java.Java; -import dev.restate.generated.service.protocol.Protocol; - -public enum MessageType { - StartMessage, - CompletionMessage, - SuspensionMessage, - ErrorMessage, - EndMessage, - EntryAckMessage, - - // IO - PollInputStreamEntryMessage, - OutputStreamEntryMessage, - - // State access - GetStateEntryMessage, - SetStateEntryMessage, - ClearStateEntryMessage, - ClearAllStateEntryMessage, - GetStateKeysEntryMessage, - - // Syscalls - SleepEntryMessage, - InvokeEntryMessage, - BackgroundInvokeEntryMessage, - AwakeableEntryMessage, - CompleteAwakeableEntryMessage, - SideEffectEntryMessage, - - // SDK specific - CombinatorAwaitableEntryMessage; - - public static final short START_MESSAGE_TYPE = 0x0000; - public static final short COMPLETION_MESSAGE_TYPE = 0x0001; - public static final short SUSPENSION_MESSAGE_TYPE = 0x0002; - public static final short ERROR_MESSAGE_TYPE = 0x0003; - public static final short ENTRY_ACK_MESSAGE_TYPE = 0x0004; - public static final short END_MESSAGE_TYPE = 0x0005; - public static final short POLL_INPUT_STREAM_ENTRY_MESSAGE_TYPE = 0x0400; - public static final short OUTPUT_STREAM_ENTRY_MESSAGE_TYPE = 0x0401; - public static final short GET_STATE_ENTRY_MESSAGE_TYPE = 0x0800; - public static final short SET_STATE_ENTRY_MESSAGE_TYPE = 0x0801; - public static final short CLEAR_STATE_ENTRY_MESSAGE_TYPE = 0x0802; - public static final short CLEAR_ALL_STATE_ENTRY_MESSAGE_TYPE = 0x0803; - public static final short GET_STATE_KEYS_ENTRY_MESSAGE_TYPE = 0x0804; - public static final short SLEEP_ENTRY_MESSAGE_TYPE = 0x0C00; - public static final short INVOKE_ENTRY_MESSAGE_TYPE = 0x0C01; - public static final short BACKGROUND_INVOKE_ENTRY_MESSAGE_TYPE = 0x0C02; - public static final short AWAKEABLE_ENTRY_MESSAGE_TYPE = 0x0C03; - public static final short COMPLETE_AWAKEABLE_ENTRY_MESSAGE_TYPE = 0x0C04; - public static final short COMBINATOR_AWAITABLE_ENTRY_MESSAGE_TYPE = (short) 0xFC00; - public static final short SIDE_EFFECT_ENTRY_MESSAGE_TYPE = (short) 0xFC01; - - public Parser messageParser() { - switch (this) { - case StartMessage: - return Protocol.StartMessage.parser(); - case CompletionMessage: - return Protocol.CompletionMessage.parser(); - case SuspensionMessage: - return Protocol.SuspensionMessage.parser(); - case EndMessage: - return Protocol.EndMessage.parser(); - case ErrorMessage: - return Protocol.ErrorMessage.parser(); - case EntryAckMessage: - return Protocol.EntryAckMessage.parser(); - case PollInputStreamEntryMessage: - return Protocol.PollInputStreamEntryMessage.parser(); - case OutputStreamEntryMessage: - return Protocol.OutputStreamEntryMessage.parser(); - case GetStateEntryMessage: - return Protocol.GetStateEntryMessage.parser(); - case SetStateEntryMessage: - return Protocol.SetStateEntryMessage.parser(); - case ClearStateEntryMessage: - return Protocol.ClearStateEntryMessage.parser(); - case ClearAllStateEntryMessage: - return Protocol.ClearAllStateEntryMessage.parser(); - case GetStateKeysEntryMessage: - return Protocol.GetStateKeysEntryMessage.parser(); - case SleepEntryMessage: - return Protocol.SleepEntryMessage.parser(); - case InvokeEntryMessage: - return Protocol.InvokeEntryMessage.parser(); - case BackgroundInvokeEntryMessage: - return Protocol.BackgroundInvokeEntryMessage.parser(); - case AwakeableEntryMessage: - return Protocol.AwakeableEntryMessage.parser(); - case CompleteAwakeableEntryMessage: - return Protocol.CompleteAwakeableEntryMessage.parser(); - case CombinatorAwaitableEntryMessage: - return Java.CombinatorAwaitableEntryMessage.parser(); - case SideEffectEntryMessage: - return Java.SideEffectEntryMessage.parser(); - } - throw new IllegalStateException(); - } - - public short encode() { - switch (this) { - case StartMessage: - return START_MESSAGE_TYPE; - case CompletionMessage: - return COMPLETION_MESSAGE_TYPE; - case SuspensionMessage: - return SUSPENSION_MESSAGE_TYPE; - case EndMessage: - return END_MESSAGE_TYPE; - case ErrorMessage: - return ERROR_MESSAGE_TYPE; - case EntryAckMessage: - return ENTRY_ACK_MESSAGE_TYPE; - case PollInputStreamEntryMessage: - return POLL_INPUT_STREAM_ENTRY_MESSAGE_TYPE; - case OutputStreamEntryMessage: - return OUTPUT_STREAM_ENTRY_MESSAGE_TYPE; - case GetStateEntryMessage: - return GET_STATE_ENTRY_MESSAGE_TYPE; - case SetStateEntryMessage: - return SET_STATE_ENTRY_MESSAGE_TYPE; - case ClearStateEntryMessage: - return CLEAR_STATE_ENTRY_MESSAGE_TYPE; - case ClearAllStateEntryMessage: - return CLEAR_ALL_STATE_ENTRY_MESSAGE_TYPE; - case GetStateKeysEntryMessage: - return GET_STATE_KEYS_ENTRY_MESSAGE_TYPE; - case SleepEntryMessage: - return SLEEP_ENTRY_MESSAGE_TYPE; - case InvokeEntryMessage: - return INVOKE_ENTRY_MESSAGE_TYPE; - case BackgroundInvokeEntryMessage: - return BACKGROUND_INVOKE_ENTRY_MESSAGE_TYPE; - case AwakeableEntryMessage: - return AWAKEABLE_ENTRY_MESSAGE_TYPE; - case CompleteAwakeableEntryMessage: - return COMPLETE_AWAKEABLE_ENTRY_MESSAGE_TYPE; - case CombinatorAwaitableEntryMessage: - return COMBINATOR_AWAITABLE_ENTRY_MESSAGE_TYPE; - case SideEffectEntryMessage: - return SIDE_EFFECT_ENTRY_MESSAGE_TYPE; - } - throw new IllegalStateException(); - } - - public static MessageType decode(short value) throws ProtocolException { - switch (value) { - case START_MESSAGE_TYPE: - return StartMessage; - case COMPLETION_MESSAGE_TYPE: - return CompletionMessage; - case SUSPENSION_MESSAGE_TYPE: - return SuspensionMessage; - case END_MESSAGE_TYPE: - return EndMessage; - case ERROR_MESSAGE_TYPE: - return ErrorMessage; - case ENTRY_ACK_MESSAGE_TYPE: - return EntryAckMessage; - case POLL_INPUT_STREAM_ENTRY_MESSAGE_TYPE: - return PollInputStreamEntryMessage; - case OUTPUT_STREAM_ENTRY_MESSAGE_TYPE: - return OutputStreamEntryMessage; - case GET_STATE_ENTRY_MESSAGE_TYPE: - return GetStateEntryMessage; - case SET_STATE_ENTRY_MESSAGE_TYPE: - return SetStateEntryMessage; - case CLEAR_STATE_ENTRY_MESSAGE_TYPE: - return ClearStateEntryMessage; - case CLEAR_ALL_STATE_ENTRY_MESSAGE_TYPE: - return ClearAllStateEntryMessage; - case GET_STATE_KEYS_ENTRY_MESSAGE_TYPE: - return GetStateKeysEntryMessage; - case SLEEP_ENTRY_MESSAGE_TYPE: - return SleepEntryMessage; - case INVOKE_ENTRY_MESSAGE_TYPE: - return InvokeEntryMessage; - case BACKGROUND_INVOKE_ENTRY_MESSAGE_TYPE: - return BackgroundInvokeEntryMessage; - case AWAKEABLE_ENTRY_MESSAGE_TYPE: - return AwakeableEntryMessage; - case COMPLETE_AWAKEABLE_ENTRY_MESSAGE_TYPE: - return CompleteAwakeableEntryMessage; - case COMBINATOR_AWAITABLE_ENTRY_MESSAGE_TYPE: - return CombinatorAwaitableEntryMessage; - case SIDE_EFFECT_ENTRY_MESSAGE_TYPE: - return SideEffectEntryMessage; - } - throw ProtocolException.unknownMessageType(value); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ProtocolException.java b/sdk-core/src/main/java/dev/restate/sdk/core/ProtocolException.java deleted file mode 100644 index f29ddc49..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ProtocolException.java +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.MessageLite; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.common.TerminalException; -import java.io.PrintWriter; -import java.io.StringWriter; - -public class ProtocolException extends RuntimeException { - - static final int JOURNAL_MISMATCH_CODE = 32; - static final int PROTOCOL_VIOLATION = 33; - - @SuppressWarnings("StaticAssignmentOfThrowable") - static final ProtocolException CLOSED = new ProtocolException("Invocation closed"); - - private final int failureCode; - - private ProtocolException(String message) { - this(message, TerminalException.Code.INTERNAL.value()); - } - - private ProtocolException(String message, int failureCode) { - this(message, null, failureCode); - } - - public ProtocolException(String message, Throwable cause, int failureCode) { - super(message, cause); - this.failureCode = failureCode; - } - - public int getFailureCode() { - return failureCode; - } - - public Protocol.ErrorMessage toErrorMessage() { - // Convert stacktrace to string - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - pw.println("Stacktrace:"); - this.printStackTrace(pw); - - return Protocol.ErrorMessage.newBuilder() - .setCode(failureCode) - .setMessage(this.toString()) - .setDescription(sw.toString()) - .build(); - } - - static ProtocolException unexpectedMessage( - Class expected, MessageLite actual) { - return new ProtocolException( - "Unexpected message type received from the runtime. Expected: '" - + expected.getCanonicalName() - + "', Actual: '" - + actual.getClass().getCanonicalName() - + "'", - PROTOCOL_VIOLATION); - } - - static ProtocolException entryDoesNotMatch(MessageLite expected, MessageLite actual) { - return new ProtocolException( - "Journal entry " + expected.getClass() + " does not match: " + expected + " != " + actual, - JOURNAL_MISMATCH_CODE); - } - - static ProtocolException completionDoesNotMatch( - String entry, Protocol.CompletionMessage.ResultCase actual) { - return new ProtocolException( - "Completion for entry " + entry + " doesn't expect completion variant " + actual, - JOURNAL_MISMATCH_CODE); - } - - static ProtocolException unknownMessageType(short type) { - return new ProtocolException( - "MessageType " + Integer.toHexString(type) + " unknown", PROTOCOL_VIOLATION); - } - - static ProtocolException methodNotFound(String componentName, String handlerName) { - return new ProtocolException( - "Cannot find handler '" + componentName + "/" + handlerName + "'", - TerminalException.Code.NOT_FOUND.value()); - } - - static ProtocolException invalidSideEffectCall() { - return new ProtocolException( - "A syscall was invoked from within a side effect closure.", - null, - TerminalException.Code.UNKNOWN.value()); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ReadyResultStateMachine.java b/sdk-core/src/main/java/dev/restate/sdk/core/ReadyResultStateMachine.java deleted file mode 100644 index 69b3d18d..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ReadyResultStateMachine.java +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.common.syscalls.Result; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** State machine tracking ready results */ -class ReadyResultStateMachine - extends BaseSuspendableCallbackStateMachine { - - private static final Logger LOG = LogManager.getLogger(ReadyResultStateMachine.class); - - interface OnNewReadyResultCallback extends SuspendableCallback { - boolean onNewResult(Map> resultMap); - } - - private final Map completions; - private final Map>> completionParsers; - private final Map> results; - - ReadyResultStateMachine() { - this.completions = new HashMap<>(); - this.completionParsers = new HashMap<>(); - this.results = new HashMap<>(); - } - - void offerCompletion(Protocol.CompletionMessage completionMessage) { - LOG.trace("Offered new completion {}", completionMessage); - - this.completions.put(completionMessage.getEntryIndex(), completionMessage); - this.tryParse(completionMessage.getEntryIndex()); - } - - void offerCompletionParser( - int entryIndex, Function> parser) { - LOG.trace("Offered new completion parser for index {}", entryIndex); - - this.completionParsers.put(entryIndex, parser); - this.tryParse(entryIndex); - } - - void onNewReadyResult(OnNewReadyResultCallback callback) { - this.assertCallbackNotSet("Two concurrent reads were requested."); - - this.tryProgress(callback); - } - - @Override - void abort(Throwable cause) { - super.abort(cause); - this.consumeCallback(this::tryProgress); - } - - private void tryParse(int entryIndex) { - Protocol.CompletionMessage completionMessage = this.completions.get(entryIndex); - if (completionMessage == null) { - return; - } - - Function> parser = - this.completionParsers.remove(entryIndex); - if (parser == null) { - return; - } - - this.completions.remove(entryIndex, completionMessage); - - // Parse to ready result - Result readyResult = parser.apply(completionMessage); - - // Push to the ready result queue - this.results.put(completionMessage.getEntryIndex(), readyResult); - - // We have a new result, let's try to progress - this.consumeCallback(this::tryProgress); - } - - private void tryProgress(OnNewReadyResultCallback cb) { - boolean resolved = cb.onNewResult(this.results); - if (!resolved) { - this.setCallback(cb); - } - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandler.java b/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandler.java deleted file mode 100644 index ad70a554..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -/** - * Resolved handler for an invocation. - * - *

You MUST first wire up the subscriber and publisher returned by {@link #input()} and {@link - * #output()} and then {@link #start()} the invocation. - */ -public interface ResolvedEndpointHandler { - - InvocationFlow.InvocationInputSubscriber input(); - - InvocationFlow.InvocationOutputPublisher output(); - - void start(); -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandlerImpl.java b/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandlerImpl.java deleted file mode 100644 index 883abf7c..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandlerImpl.java +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.common.syscalls.InvocationHandler; -import dev.restate.sdk.common.syscalls.SyscallCallback; -import dev.restate.sdk.common.syscalls.Syscalls; -import java.util.concurrent.Executor; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jspecify.annotations.Nullable; - -final class ResolvedEndpointHandlerImpl implements ResolvedEndpointHandler { - - private static final Logger LOG = LogManager.getLogger(ResolvedEndpointHandlerImpl.class); - - private final InvocationStateMachine stateMachine; - private final RestateEndpoint.LoggingContextSetter loggingContextSetter; - private final SyscallsInternal syscalls; - private final InvocationHandler wrappedHandler; - - public ResolvedEndpointHandlerImpl( - InvocationStateMachine stateMachine, - RestateEndpoint.LoggingContextSetter loggingContextSetter, - SyscallsInternal syscalls, - InvocationHandler handler, - @Nullable Executor userCodeExecutor) { - this.stateMachine = stateMachine; - this.loggingContextSetter = loggingContextSetter; - this.syscalls = syscalls; - this.wrappedHandler = - userCodeExecutor == null - ? new InvocationHandlerWrapper(handler) - : new ExecutorSwitchingInvocationHandlerWrapper(handler, userCodeExecutor); - } - - @Override - public InvocationFlow.InvocationInputSubscriber input() { - return new ExceptionCatchingInvocationInputSubscriber(stateMachine); - } - - @Override - public InvocationFlow.InvocationOutputPublisher output() { - return stateMachine; - } - - @Override - public void start() { - LOG.info("Start processing invocation"); - stateMachine.start( - invocationId -> { - // Set invocation id in logging context - loggingContextSetter.setInvocationId(invocationId.toString()); - - // pollInput then invoke the wrappedHandler - syscalls.pollInputAndResolve( - SyscallCallback.of( - pollInputReadyResult -> { - if (pollInputReadyResult.isSuccess()) { - final Object message = pollInputReadyResult.getValue(); - LOG.trace("Read input message:\n{}", message); - - wrappedHandler.handle( - syscalls, - pollInputReadyResult.getValue(), - SyscallCallback.of(this::writeOutputAndEnd, this::end)); - } else { - // PollInputStream failed. - // This is probably a cancellation. - this.end(pollInputReadyResult.getFailure()); - } - }, - syscalls::fail)); - }); - } - - private void writeOutputAndEnd(ByteString output) { - syscalls.writeOutput( - output, - SyscallCallback.ofVoid( - () -> { - LOG.trace("Wrote output message:\n{}", output); - this.end(); - }, - syscalls::fail)); - } - - private void end() { - this.end(null); - } - - private void end(@Nullable Throwable exception) { - if (exception == null || Util.containsSuspendedException(exception)) { - syscalls.close(); - } else { - if (Util.isTerminalException(exception)) { - syscalls.writeOutput( - (TerminalException) exception, - SyscallCallback.ofVoid( - () -> { - LOG.trace("Closed correctly with non ok exception", exception); - syscalls.close(); - }, - syscalls::fail)); - } else { - syscalls.fail(exception); - } - } - } - - private static class InvocationHandlerWrapper implements InvocationHandler { - - private final InvocationHandler handler; - - private InvocationHandlerWrapper(InvocationHandler handler) { - this.handler = handler; - } - - @Override - public void handle(Syscalls syscalls, ByteString input, SyscallCallback callback) { - try { - this.handler.handle(syscalls, input, callback); - } catch (Throwable e) { - LOG.warn("Error when processing the invocation", e); - callback.onCancel(e); - } - } - } - - private static class ExecutorSwitchingInvocationHandlerWrapper implements InvocationHandler { - private final InvocationHandler handler; - private final Executor userCodeExecutor; - - private ExecutorSwitchingInvocationHandlerWrapper( - InvocationHandler handler, Executor userCodeExecutor) { - this.handler = handler; - this.userCodeExecutor = userCodeExecutor; - } - - @Override - public void handle(Syscalls syscalls, ByteString input, SyscallCallback callback) { - userCodeExecutor.execute( - () -> { - try { - this.handler.handle(syscalls, input, callback); - } catch (Throwable e) { - LOG.warn("Error when processing the invocation", e); - callback.onCancel(e); - } - }); - } - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java b/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java deleted file mode 100644 index 0802a763..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import dev.restate.sdk.common.ComponentAdapter; -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import dev.restate.sdk.common.syscalls.HandlerDefinition; -import dev.restate.sdk.core.manifest.Component; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.*; -import java.util.concurrent.Executor; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; -import org.jspecify.annotations.Nullable; - -public class RestateEndpoint { - - private static final Logger LOG = LogManager.getLogger(RestateEndpoint.class); - - private final Map components; - private final Tracer tracer; - private final DeploymentManifest deploymentManifest; - - private RestateEndpoint( - DeploymentManifestSchema.ProtocolMode protocolMode, - Map components, - Tracer tracer) { - this.components = components; - this.tracer = tracer; - this.deploymentManifest = new DeploymentManifest(protocolMode, components); - - this.logCreation(); - } - - public ResolvedEndpointHandler resolve( - String componentName, - String handlerName, - io.opentelemetry.context.Context otelContext, - LoggingContextSetter loggingContextSetter, - @Nullable Executor syscallExecutor, - @Nullable Executor userCodeExecutor) - throws ProtocolException { - // Resolve the service method definition - ComponentDefinition svc = this.components.get(componentName); - if (svc == null) { - throw ProtocolException.methodNotFound(componentName, handlerName); - } - String fullyQualifiedServiceMethod = componentName + "/" + handlerName; - HandlerDefinition handler = svc.getHandler(handlerName); - if (handler == null) { - throw ProtocolException.methodNotFound(componentName, handlerName); - } - - // Generate the span - Span span = - tracer - .spanBuilder("Invoke method") - .setSpanKind(SpanKind.SERVER) - .setParent(otelContext) - .setAttribute(SemanticAttributes.RPC_SYSTEM, "restate") - .setAttribute(SemanticAttributes.RPC_SERVICE, componentName) - .setAttribute(SemanticAttributes.RPC_METHOD, handlerName) - .startSpan(); - - // Setup logging context - loggingContextSetter.setServiceMethod(fullyQualifiedServiceMethod); - - // Instantiate state machine, syscall and grpc bridge - InvocationStateMachine stateMachine = - new InvocationStateMachine( - componentName, - fullyQualifiedServiceMethod, - span, - s -> loggingContextSetter.setInvocationStatus(s.toString())); - SyscallsInternal syscalls = - syscallExecutor != null - ? new ExecutorSwitchingSyscalls(new SyscallsImpl(stateMachine), syscallExecutor) - : new SyscallsImpl(stateMachine); - - return new ResolvedEndpointHandlerImpl( - stateMachine, loggingContextSetter, syscalls, handler.getHandler(), userCodeExecutor); - } - - public DeploymentManifestSchema handleDiscoveryRequest() { - DeploymentManifestSchema response = this.deploymentManifest.manifest(); - LOG.info( - "Replying to discovery request with components [{}]", - response.getComponents().stream() - .map(Component::getFullyQualifiedComponentName) - .collect(Collectors.joining(","))); - return response; - } - - private void logCreation() { - LOG.info("Registered components: {}", this.components.keySet()); - } - - // -- Builder - - public static Builder newBuilder(DeploymentManifestSchema.ProtocolMode protocolMode) { - return new Builder(protocolMode); - } - - public static class Builder { - - private final List components = new ArrayList<>(); - private final DeploymentManifestSchema.ProtocolMode protocolMode; - private Tracer tracer = OpenTelemetry.noop().getTracer("NOOP"); - - public Builder(DeploymentManifestSchema.ProtocolMode protocolMode) { - this.protocolMode = protocolMode; - } - - public Builder with(ComponentDefinition component) { - this.components.add(component); - return this; - } - - public Builder withTracer(Tracer tracer) { - this.tracer = tracer; - return this; - } - - public RestateEndpoint build() { - return new RestateEndpoint( - this.protocolMode, - this.components.stream() - .collect( - Collectors.toMap( - ComponentDefinition::getFullyQualifiedServiceName, Function.identity())), - tracer); - } - } - - /** - * Interface to abstract setting the logging context variables. - * - *

In classic multithreaded environments, you can just use {@link - * LoggingContextSetter#THREAD_LOCAL_INSTANCE}, though the caller of {@link RestateEndpoint} must - * take care of the cleanup of the thread local map. - */ - public interface LoggingContextSetter { - - String INVOCATION_ID_KEY = "restateInvocationId"; - String COMPONENT_HANDLER_KEY = "restateComponentHandler"; - String INVOCATION_STATUS_KEY = "restateInvocationStatus"; - - LoggingContextSetter THREAD_LOCAL_INSTANCE = - new LoggingContextSetter() { - @Override - public void setServiceMethod(String serviceMethod) { - ThreadContext.put(COMPONENT_HANDLER_KEY, serviceMethod); - } - - @Override - public void setInvocationId(String id) { - ThreadContext.put(INVOCATION_ID_KEY, id); - } - - @Override - public void setInvocationStatus(String invocationStatus) { - ThreadContext.put(INVOCATION_STATUS_KEY, invocationStatus); - } - }; - - void setServiceMethod(String serviceMethod); - - void setInvocationId(String id); - - void setInvocationStatus(String invocationStatus); - } - - private static class ComponentAdapterSingleton { - private static final ComponentAdapterDiscovery INSTANCE = new ComponentAdapterDiscovery(); - } - - @SuppressWarnings("rawtypes") - private static class ComponentAdapterDiscovery { - - private final List adapters; - - private ComponentAdapterDiscovery() { - this.adapters = - ServiceLoader.load(ComponentAdapter.class).stream() - .map(ServiceLoader.Provider::get) - .collect(Collectors.toList()); - } - - private @Nullable ComponentAdapter discoverAdapter(Object service) { - return this.adapters.stream() - .filter(sa -> sa.supportsObject(service)) - .findFirst() - .orElse(null); - } - } - - /** Resolve the code generated {@link ComponentAdapter} */ - @SuppressWarnings("unchecked") - public static ComponentAdapter discoverAdapter(Object component) { - return Objects.requireNonNull( - ComponentAdapterSingleton.INSTANCE.discoverAdapter(component), - () -> - "ComponentAdapter class not found for service " - + component.getClass().getCanonicalName() - + ". " - + "Make sure the annotation processor is correctly configured to generate the ComponentAdapter, " - + "and it generates the META-INF/services/" - + ComponentAdapter.class.getCanonicalName() - + " file containing the generated class. " - + "If you're using fat jars, make sure the jar plugin correctly squashes all the META-INF/services files. " - + "Found ComponentAdapter: " - + ComponentAdapterSingleton.INSTANCE.adapters); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/SideEffectAckStateMachine.java b/sdk-core/src/main/java/dev/restate/sdk/core/SideEffectAckStateMachine.java deleted file mode 100644 index 3fd0ed0d..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/SideEffectAckStateMachine.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -/** State machine tracking side effects acks */ -class SideEffectAckStateMachine - extends BaseSuspendableCallbackStateMachine { - - interface SideEffectAckCallback extends SuspendableCallback { - void onLastSideEffectAck(); - } - - private int lastAcknowledgedEntry = -1; - - /** -1 means no side effect waiting to be acked. */ - private int lastExecutedSideEffect = -1; - - void waitLastSideEffectAck(SideEffectAckCallback callback) { - if (canExecuteSideEffect()) { - callback.onLastSideEffectAck(); - } else { - this.setCallback(callback); - } - } - - void tryHandleSideEffectAck(int entryIndex) { - this.lastAcknowledgedEntry = Math.max(entryIndex, this.lastAcknowledgedEntry); - if (canExecuteSideEffect()) { - this.consumeCallback(SideEffectAckCallback::onLastSideEffectAck); - } - } - - void registerExecutedSideEffect(int entryIndex) { - this.lastExecutedSideEffect = entryIndex; - } - - private boolean canExecuteSideEffect() { - return this.lastExecutedSideEffect <= this.lastAcknowledgedEntry; - } - - public int getLastExecutedSideEffect() { - return lastExecutedSideEffect; - } - - @Override - void abort(Throwable cause) { - super.abort(cause); - // We can't do anything else if the input stream is closed, so we just fail the callback, if any - this.tryFailCallback(); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/SuspendableCallback.java b/sdk-core/src/main/java/dev/restate/sdk/core/SuspendableCallback.java deleted file mode 100644 index 8939bb26..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/SuspendableCallback.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -interface SuspendableCallback { - - void onSuspend(); - - void onError(Throwable e); -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsImpl.java b/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsImpl.java deleted file mode 100644 index 56f6b6be..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsImpl.java +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import dev.restate.generated.sdk.java.Java; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.generated.service.protocol.Protocol.PollInputStreamEntryMessage; -import dev.restate.sdk.common.InvocationId; -import dev.restate.sdk.common.Target; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.common.syscalls.*; -import dev.restate.sdk.core.DeferredResults.SingleDeferredInternal; -import dev.restate.sdk.core.Entries.*; -import java.nio.ByteBuffer; -import java.time.Duration; -import java.time.Instant; -import java.util.AbstractMap; -import java.util.Base64; -import java.util.Collection; -import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jspecify.annotations.Nullable; - -public final class SyscallsImpl implements SyscallsInternal { - - private static final Logger LOG = LogManager.getLogger(SyscallsImpl.class); - - private final InvocationStateMachine stateMachine; - - SyscallsImpl(InvocationStateMachine stateMachine) { - this.stateMachine = stateMachine; - } - - @Override - public InvocationId invocationId() { - return this.stateMachine.invocationId(); - } - - @Override - public String objectKey() { - return this.stateMachine.objectKey(); - } - - @Override - public void pollInput(SyscallCallback> callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("pollInput"); - this.stateMachine.processCompletableJournalEntry( - PollInputStreamEntryMessage.getDefaultInstance(), PollInputEntry.INSTANCE, callback); - }, - callback); - } - - @Override - public void writeOutput(ByteString value, SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("writeOutput success"); - this.writeOutput( - Protocol.OutputStreamEntryMessage.newBuilder().setValue(value).build(), callback); - }, - callback); - } - - @Override - public void writeOutput(TerminalException throwable, SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("writeOutput failure"); - this.writeOutput( - Protocol.OutputStreamEntryMessage.newBuilder() - .setFailure(Util.toProtocolFailure(throwable)) - .build(), - callback); - }, - callback); - } - - private void writeOutput( - Protocol.OutputStreamEntryMessage entry, SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> this.stateMachine.processJournalEntry(entry, OutputStreamEntry.INSTANCE, callback), - callback); - } - - @Override - public void get(String name, SyscallCallback> callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("get {}", name); - this.stateMachine.processCompletableJournalEntry( - Protocol.GetStateEntryMessage.newBuilder() - .setKey(ByteString.copyFromUtf8(name)) - .build(), - GetStateEntry.INSTANCE, - callback); - }, - callback); - } - - @Override - public void getKeys(SyscallCallback>> callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("get keys"); - this.stateMachine.processCompletableJournalEntry( - Protocol.GetStateKeysEntryMessage.newBuilder().build(), - GetStateKeysEntry.INSTANCE, - callback); - }, - callback); - } - - @Override - public void clear(String name, SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("clear {}", name); - this.stateMachine.processJournalEntry( - Protocol.ClearStateEntryMessage.newBuilder() - .setKey(ByteString.copyFromUtf8(name)) - .build(), - ClearStateEntry.INSTANCE, - callback); - }, - callback); - } - - @Override - public void clearAll(SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("clearAll"); - this.stateMachine.processJournalEntry( - Protocol.ClearAllStateEntryMessage.newBuilder().build(), - ClearAllStateEntry.INSTANCE, - callback); - }, - callback); - } - - @Override - public void set(String name, ByteString value, SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("set {}", name); - this.stateMachine.processJournalEntry( - Protocol.SetStateEntryMessage.newBuilder() - .setKey(ByteString.copyFromUtf8(name)) - .setValue(value) - .build(), - SetStateEntry.INSTANCE, - callback); - }, - callback); - } - - @Override - public void sleep(Duration duration, SyscallCallback> callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("sleep {}", duration); - this.stateMachine.processCompletableJournalEntry( - Protocol.SleepEntryMessage.newBuilder() - .setWakeUpTime(Instant.now().toEpochMilli() + duration.toMillis()) - .build(), - SleepEntry.INSTANCE, - callback); - }, - callback); - } - - @Override - public void call( - Target target, ByteString parameter, SyscallCallback> callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("call {}", target); - - Protocol.InvokeEntryMessage.Builder builder = - Protocol.InvokeEntryMessage.newBuilder() - .setServiceName(target.getComponent()) - .setMethodName(target.getHandler()) - .setParameter(parameter); - if (target.getKey() != null) { - builder.setKey(target.getKey()); - } - - this.stateMachine.processCompletableJournalEntry( - builder.build(), new InvokeEntry<>(Result::success), callback); - }, - callback); - } - - @Override - public void send( - Target target, - ByteString parameter, - @Nullable Duration delay, - SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("backgroundCall {}", target); - - Protocol.BackgroundInvokeEntryMessage.Builder builder = - Protocol.BackgroundInvokeEntryMessage.newBuilder() - .setServiceName(target.getComponent()) - .setMethodName(target.getHandler()) - .setParameter(parameter); - if (target.getKey() != null) { - builder.setKey(target.getKey()); - } - if (delay != null) { - builder.setInvokeTime(Instant.now().toEpochMilli() + delay.toMillis()); - } - - this.stateMachine.processJournalEntry( - builder.build(), BackgroundInvokeEntry.INSTANCE, callback); - }, - callback); - } - - @Override - public void enterSideEffectBlock(EnterSideEffectSyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("enterSideEffectBlock"); - this.stateMachine.enterSideEffectBlock(callback); - }, - callback); - } - - @Override - public void exitSideEffectBlock(ByteString toWrite, ExitSideEffectSyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("exitSideEffectBlock with success"); - this.stateMachine.exitSideEffectBlock( - Java.SideEffectEntryMessage.newBuilder().setValue(toWrite).build(), callback); - }, - callback); - } - - @Override - public void exitSideEffectBlockWithTerminalException( - TerminalException toWrite, ExitSideEffectSyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("exitSideEffectBlock with failure"); - this.stateMachine.exitSideEffectBlock( - Java.SideEffectEntryMessage.newBuilder() - .setFailure(Util.toProtocolFailure(toWrite)) - .build(), - callback); - }, - callback); - } - - @Override - public void awakeable(SyscallCallback>> callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("awakeable"); - this.stateMachine.processCompletableJournalEntry( - Protocol.AwakeableEntryMessage.getDefaultInstance(), - AwakeableEntry.INSTANCE, - SyscallCallback.mappingTo( - deferredResult -> { - // Encode awakeable id - ByteString awakeableId = - stateMachine - .id() - .concat( - ByteString.copyFrom( - ByteBuffer.allocate(4) - .putInt( - ((SingleDeferredInternal) deferredResult) - .entryIndex()) - .rewind())); - - return new AbstractMap.SimpleImmutableEntry<>( - Entries.AWAKEABLE_IDENTIFIER_PREFIX - + Base64.getUrlEncoder().encodeToString(awakeableId.toByteArray()), - deferredResult); - }, - callback)); - }, - callback); - } - - @Override - public void resolveAwakeable( - String serializedId, ByteString payload, SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("resolveAwakeable"); - completeAwakeable( - serializedId, - Protocol.CompleteAwakeableEntryMessage.newBuilder().setValue(payload), - callback); - }, - callback); - } - - @Override - public void rejectAwakeable(String serializedId, String reason, SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> { - LOG.trace("rejectAwakeable"); - completeAwakeable( - serializedId, - Protocol.CompleteAwakeableEntryMessage.newBuilder() - .setFailure( - Protocol.Failure.newBuilder() - .setCode(TerminalException.Code.UNKNOWN.value()) - .setMessage(reason)), - callback); - }, - callback); - } - - private void completeAwakeable( - String serializedId, - Protocol.CompleteAwakeableEntryMessage.Builder builder, - SyscallCallback callback) { - Protocol.CompleteAwakeableEntryMessage expectedEntry = builder.setId(serializedId).build(); - this.stateMachine.processJournalEntry(expectedEntry, CompleteAwakeableEntry.INSTANCE, callback); - } - - @Override - public void resolveDeferred(Deferred deferredToResolve, SyscallCallback callback) { - wrapAndPropagateExceptions( - () -> this.stateMachine.resolveDeferred(deferredToResolve, callback), callback); - } - - @Override - public String getFullyQualifiedMethodName() { - return this.stateMachine.getFullyQualifiedMethodName(); - } - - @Override - public InvocationState getInvocationState() { - return this.stateMachine.getInvocationState(); - } - - @Override - public boolean isInsideSideEffect() { - return this.stateMachine.isInsideSideEffect(); - } - - @Override - public void close() { - this.stateMachine.end(); - } - - @Override - public void fail(Throwable cause) { - this.stateMachine.fail(cause); - } - - // -- Wrapper for failure propagation - - private void wrapAndPropagateExceptions(Runnable r, SyscallCallback handler) { - try { - r.run(); - } catch (Throwable e) { - this.fail(e); - handler.onCancel(e); - } - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsInternal.java b/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsInternal.java deleted file mode 100644 index 3352bfcb..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/SyscallsInternal.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.Result; -import dev.restate.sdk.common.syscalls.SyscallCallback; -import dev.restate.sdk.common.syscalls.Syscalls; -import dev.restate.sdk.core.DeferredResults.DeferredInternal; -import java.util.List; -import java.util.stream.Collectors; - -interface SyscallsInternal extends Syscalls { - - @Override - default Deferred createAnyDeferred(List> children) { - return DeferredResults.any( - children.stream().map(dr -> (DeferredInternal) dr).collect(Collectors.toList())); - } - - @Override - default Deferred createAllDeferred(List> children) { - return DeferredResults.all( - children.stream().map(dr -> (DeferredInternal) dr).collect(Collectors.toList())); - } - - // -- Helper for pollInput - default void pollInputAndResolve(SyscallCallback> callback) { - this.pollInput( - SyscallCallback.of( - deferredValue -> - this.resolveDeferred( - deferredValue, - SyscallCallback.ofVoid( - () -> callback.onSuccess(deferredValue.toResult()), callback::onCancel)), - callback::onCancel)); - } - - // -- Lifecycle methods - - void close(); - - // -- State machine introspection (used by logging propagator) - - /** - * @return fully qualified method name in the form {fullyQualifiedServiceName}/{methodName} - */ - String getFullyQualifiedMethodName(); - - InvocationState getInvocationState(); -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/Tracing.java b/sdk-core/src/main/java/dev/restate/sdk/core/Tracing.java deleted file mode 100644 index 1a5f41bb..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/Tracing.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import io.opentelemetry.api.common.AttributeKey; - -final class Tracing { - - private Tracing() {} - - static AttributeKey RESTATE_INVOCATION_ID = - AttributeKey.stringKey("restate.invocation.id"); - - static AttributeKey RESTATE_STATE_KEY = AttributeKey.stringKey("restate.state.key"); - static AttributeKey RESTATE_SLEEP_WAKE_UP_TIME = - AttributeKey.longKey("restate.sleep.wake_up_time"); - - static AttributeKey RESTATE_COORDINATION_CALL_SERVICE = - AttributeKey.stringKey("restate.coordination.call.service"); - static AttributeKey RESTATE_COORDINATION_CALL_METHOD = - AttributeKey.stringKey("restate.coordination.call.method"); -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/UserStateStore.java b/sdk-core/src/main/java/dev/restate/sdk/core/UserStateStore.java deleted file mode 100644 index e8bf7b63..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/UserStateStore.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -final class UserStateStore { - - interface State {} - - static final class Unknown implements State { - private static final Unknown INSTANCE = new Unknown(); - - private Unknown() {} - } - - static final class Empty implements State { - private static final Empty INSTANCE = new Empty(); - - private Empty() {} - } - - static final class Value implements State { - private final ByteString value; - - private Value(ByteString value) { - this.value = value; - } - - public ByteString getValue() { - return value; - } - } - - private boolean isPartial; - private final HashMap map; - - UserStateStore(boolean isPartial, Map map) { - this.isPartial = isPartial; - this.map = - new HashMap<>( - map.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> new Value(e.getValue())))); - } - - public State get(ByteString key) { - return this.map.getOrDefault(key, isPartial ? Unknown.INSTANCE : Empty.INSTANCE); - } - - public void set(ByteString key, ByteString value) { - this.map.put(key, new Value(value)); - } - - public void clear(ByteString key) { - this.map.put(key, Empty.INSTANCE); - } - - public void clearAll() { - this.map.clear(); - this.isPartial = false; - } - - public boolean isComplete() { - return !isPartial; - } - - public Set keys() { - return this.map.keySet(); - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/Util.java b/sdk-core/src/main/java/dev/restate/sdk/core/Util.java deleted file mode 100644 index af6874d6..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/Util.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.MessageLite; -import dev.restate.generated.sdk.java.Java; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.common.AbortedExecutionException; -import dev.restate.sdk.common.TerminalException; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Predicate; - -public final class Util { - private Util() {} - - @SuppressWarnings("unchecked") - static void sneakyThrow(Throwable e) throws E { - throw (E) e; - } - - /** - * Finds a throwable fulfilling the condition in the cause chain of the given throwable. If there - * is none, then the method returns an empty optional. - * - * @param throwable to check for the given condition - * @param condition condition that a cause needs to fulfill - * @return Some cause that fulfills the condition; otherwise an empty optional - */ - @SuppressWarnings("unchecked") - static Optional findCause( - Throwable throwable, Predicate condition) { - Throwable currentThrowable = throwable; - - while (currentThrowable != null) { - if (condition.test(currentThrowable)) { - return (Optional) Optional.of(currentThrowable); - } - - if (currentThrowable == currentThrowable.getCause()) { - break; - } else { - currentThrowable = currentThrowable.getCause(); - } - } - - return Optional.empty(); - } - - public static Optional findProtocolException(Throwable throwable) { - return findCause(throwable, t -> t instanceof ProtocolException); - } - - public static boolean containsSuspendedException(Throwable throwable) { - return findCause(throwable, t -> t == AbortedExecutionException.INSTANCE).isPresent(); - } - - static Protocol.Failure toProtocolFailure(TerminalException.Code code, String message) { - Protocol.Failure.Builder builder = Protocol.Failure.newBuilder().setCode(code.value()); - if (message != null) { - builder.setMessage(message); - } - return builder.build(); - } - - static Protocol.Failure toProtocolFailure(Throwable throwable) { - if (throwable instanceof TerminalException) { - return toProtocolFailure(((TerminalException) throwable).getCode(), throwable.getMessage()); - } - return toProtocolFailure(TerminalException.Code.UNKNOWN, throwable.toString()); - } - - static TerminalException toRestateException(Protocol.Failure failure) { - return new TerminalException( - TerminalException.Code.fromValue(failure.getCode()), failure.getMessage()); - } - - static boolean isTerminalException(Throwable throwable) { - return throwable instanceof TerminalException; - } - - static void assertIsEntry(MessageLite msg) { - if (!isEntry(msg)) { - throw new IllegalStateException("Expected input to be entry: " + msg); - } - } - - static void assertEntryEquals(MessageLite expected, MessageLite actual) { - if (!Objects.equals(expected, actual)) { - throw ProtocolException.entryDoesNotMatch(expected, actual); - } - } - - static void assertEntryClass(Class clazz, MessageLite actual) { - if (!clazz.equals(actual.getClass())) { - throw ProtocolException.unexpectedMessage(clazz, actual); - } - } - - static boolean isEntry(MessageLite msg) { - return msg instanceof Protocol.PollInputStreamEntryMessage - || msg instanceof Protocol.OutputStreamEntryMessage - || msg instanceof Protocol.GetStateEntryMessage - || msg instanceof Protocol.GetStateKeysEntryMessage - || msg instanceof Protocol.SetStateEntryMessage - || msg instanceof Protocol.ClearStateEntryMessage - || msg instanceof Protocol.ClearAllStateEntryMessage - || msg instanceof Protocol.SleepEntryMessage - || msg instanceof Protocol.InvokeEntryMessage - || msg instanceof Protocol.BackgroundInvokeEntryMessage - || msg instanceof Protocol.AwakeableEntryMessage - || msg instanceof Protocol.CompleteAwakeableEntryMessage - || msg instanceof Java.CombinatorAwaitableEntryMessage - || msg instanceof Java.SideEffectEntryMessage; - } -} diff --git a/sdk-core/src/main/sdk-proto/dev/restate/sdk/java.proto b/sdk-core/src/main/sdk-proto/dev/restate/sdk/java.proto deleted file mode 100644 index 0e31615e..00000000 --- a/sdk-core/src/main/sdk-proto/dev/restate/sdk/java.proto +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -syntax = "proto3"; - -package dev.restate.sdk.java; - -import "google/protobuf/any.proto"; -import "dev/restate/service/protocol.proto"; - -option java_package = "dev.restate.generated.sdk.java"; - -// Type: 0xFC00 + 0 -message CombinatorAwaitableEntryMessage { - repeated uint32 entry_index = 1; -} - -// Type: 0xFC00 + 1 -// Flag: RequiresRuntimeAck -message SideEffectEntryMessage { - oneof result { - bytes value = 14; - dev.restate.service.protocol.Failure failure = 15; - }; -} diff --git a/sdk-core/src/main/service-protocol/.gitignore b/sdk-core/src/main/service-protocol/.gitignore deleted file mode 100644 index 29b636a4..00000000 --- a/sdk-core/src/main/service-protocol/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.idea -*.iml \ No newline at end of file diff --git a/sdk-core/src/main/service-protocol/LICENSE b/sdk-core/src/main/service-protocol/LICENSE deleted file mode 100644 index b81eecf5..00000000 --- a/sdk-core/src/main/service-protocol/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 - Restate Software, Inc., Restate GmbH - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE \ No newline at end of file diff --git a/sdk-core/src/main/service-protocol/README.md b/sdk-core/src/main/service-protocol/README.md deleted file mode 100644 index 9ff013d0..00000000 --- a/sdk-core/src/main/service-protocol/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Restate Service Protocol - -This repo contains specification documents and Protobuf schemas of the Restate Service Protocol. - -* [Service invocation protocol specification](./service-invocation-protocol.md) - -## Development - -To format the spec document: - -``` -npx prettier -w service-invocation-protocol.md -``` \ No newline at end of file diff --git a/sdk-core/src/main/service-protocol/deployment_manifest_schema.json b/sdk-core/src/main/service-protocol/deployment_manifest_schema.json deleted file mode 100644 index 87f04fe0..00000000 --- a/sdk-core/src/main/service-protocol/deployment_manifest_schema.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "$id": "https://restate.dev/deployment.manifest.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "title": "Deployment", - "description": "Restate deployment manifest", - "properties": { - "protocolMode": { - "title": "ProtocolMode", - "enum": ["BIDI_STREAM", "REQUEST_RESPONSE"] - }, - "minProtocolVersion": { - "type": "integer", - "minimum": 0 - }, - "maxProtocolVersion": { - "type": "integer", - "maximum": 0 - }, - "components": { - "type": "array", - "items": { - "type": "object", - "title": "Component", - "properties": { - "fullyQualifiedComponentName": { - "type": "string", - "pattern": "^([a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z0-9._-]*$" - }, - "componentType": { - "title": "ComponentType", - "enum": ["VIRTUAL_OBJECT", "SERVICE"] - }, - "handlers": { - "type": "array", - "items": { - "type": "object", - "title": "Handler", - "properties": { - "name": { - "type": "string", - "pattern": "^([a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z0-9_]*$" - }, - "inputSchema": {}, - "outputSchema": {} - }, - "required": [ "name" ], - "additionalProperties": false - } - } - }, - "required": [ "fullyQualifiedComponentName","componentType", "handlers" ], - "additionalProperties": false - } - } - }, - "required": [ "minProtocolVersion", "maxProtocolVersion", "components" ], - "additionalProperties": false -} \ No newline at end of file diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/AssertUtils.java b/sdk-core/src/test/java/dev/restate/sdk/core/AssertUtils.java deleted file mode 100644 index 1db52887..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/AssertUtils.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.STRING; -import static org.assertj.core.api.InstanceOfAssertFactories.type; - -import com.google.protobuf.MessageLite; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.common.TerminalException; -import java.util.List; -import java.util.function.Consumer; - -public class AssertUtils { - - public static Consumer> containsOnly(Consumer consumer) { - return msgs -> assertThat(msgs).satisfiesExactly(consumer); - } - - public static Consumer> containsOnlyExactErrorMessage(Throwable e) { - return containsOnly(exactErrorMessage(e)); - } - - public static Consumer errorMessage( - Consumer consumer) { - return msg -> - assertThat(msg).asInstanceOf(type(Protocol.ErrorMessage.class)).satisfies(consumer); - } - - public static Consumer exactErrorMessage(Throwable e) { - return errorMessage( - msg -> - assertThat(msg) - .returns(e.toString(), Protocol.ErrorMessage::getMessage) - .returns(TerminalException.Code.UNKNOWN.value(), Protocol.ErrorMessage::getCode)); - } - - public static Consumer errorMessageStartingWith(String str) { - return errorMessage( - msg -> - assertThat(msg).extracting(Protocol.ErrorMessage::getMessage, STRING).startsWith(str)); - } - - public static Consumer protocolExceptionErrorMessage(int code) { - return errorMessage( - msg -> - assertThat(msg) - .returns(code, Protocol.ErrorMessage::getCode) - .extracting(Protocol.ErrorMessage::getMessage, STRING) - .startsWith(ProtocolException.class.getCanonicalName())); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/AwakeableIdTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/AwakeableIdTestSuite.java deleted file mode 100644 index 70942c7c..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/AwakeableIdTestSuite.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.ProtoUtils.inputMessage; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.type; - -import com.google.protobuf.ByteString; -import dev.restate.generated.service.protocol.Protocol.AwakeableEntryMessage; -import dev.restate.generated.service.protocol.Protocol.OutputStreamEntryMessage; -import dev.restate.generated.service.protocol.Protocol.StartMessage; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.TestDefinitions.TestDefinition; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import java.nio.ByteBuffer; -import java.util.Base64; -import java.util.UUID; -import java.util.stream.Stream; - -public abstract class AwakeableIdTestSuite implements TestSuite { - - protected abstract TestDefinitions.TestInvocationBuilder returnAwakeableId(); - - @Override - public Stream definitions() { - UUID id = UUID.randomUUID(); - String debugId = id.toString(); - byte[] serializedId = serializeUUID(id); - - ByteBuffer expectedAwakeableId = ByteBuffer.allocate(serializedId.length + 4); - expectedAwakeableId.put(serializedId); - expectedAwakeableId.putInt(1); - expectedAwakeableId.rewind(); - String base64ExpectedAwakeableId = - Entries.AWAKEABLE_IDENTIFIER_PREFIX - + Base64.getUrlEncoder().encodeToString(expectedAwakeableId.array()); - - return Stream.of( - returnAwakeableId() - .withInput( - StartMessage.newBuilder() - .setDebugId(debugId) - .setId(ByteString.copyFrom(serializedId)) - .setKnownEntries(1), - inputMessage()) - .assertingOutput( - messages -> { - assertThat(messages).element(0).isInstanceOf(AwakeableEntryMessage.class); - assertThat(messages) - .element(1) - .asInstanceOf(type(OutputStreamEntryMessage.class)) - .extracting(out -> CoreSerdes.JSON_STRING.deserialize(out.getValue())) - .isEqualTo(base64ExpectedAwakeableId); - })); - } - - private byte[] serializeUUID(UUID uuid) { - ByteBuffer serializedId = ByteBuffer.allocate(16); - serializedId.putLong(uuid.getMostSignificantBits()); - serializedId.putLong(uuid.getLeastSignificantBits()); - serializedId.rewind(); - return serializedId.array(); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java b/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java deleted file mode 100644 index 9e372bbc..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static org.assertj.core.api.Assertions.assertThat; - -import dev.restate.sdk.common.ComponentType; -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import dev.restate.sdk.common.syscalls.ExecutorType; -import dev.restate.sdk.common.syscalls.HandlerDefinition; -import dev.restate.sdk.core.manifest.Component; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema.ProtocolMode; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; - -class ComponentDiscoveryHandlerTest { - - @Test - void handleWithMultipleServices() { - DeploymentManifest deploymentManifest = - new DeploymentManifest( - ProtocolMode.REQUEST_RESPONSE, - Map.of( - "MyGreeter", - new ComponentDefinition( - "MyGreeter", - ExecutorType.BLOCKING, - ComponentType.SERVICE, - List.of(new HandlerDefinition("greet", null, null, null))))); - - DeploymentManifestSchema manifest = deploymentManifest.manifest(); - - assertThat(manifest.getComponents()) - .extracting(Component::getFullyQualifiedComponentName) - .containsOnly("MyGreeter"); - assertThat(manifest.getProtocolMode()).isEqualTo(ProtocolMode.REQUEST_RESPONSE); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/DeferredTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/DeferredTestSuite.java deleted file mode 100644 index af51bdcd..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/DeferredTestSuite.java +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.list; -import static org.assertj.core.api.InstanceOfAssertFactories.type; - -import com.google.protobuf.Empty; -import dev.restate.generated.sdk.java.Java; -import dev.restate.generated.service.protocol.Protocol; -import java.util.function.Supplier; -import java.util.stream.Stream; - -public abstract class DeferredTestSuite implements TestSuite { - - protected abstract TestInvocationBuilder reverseAwaitOrder(); - - protected abstract TestInvocationBuilder awaitTwiceTheSameAwaitable(); - - protected abstract TestInvocationBuilder awaitAll(); - - protected abstract TestInvocationBuilder awaitAny(); - - protected abstract TestInvocationBuilder combineAnyWithAll(); - - protected abstract TestInvocationBuilder awaitAnyIndex(); - - protected abstract TestInvocationBuilder awaitOnAlreadyResolvedAwaitables(); - - protected abstract TestInvocationBuilder awaitWithTimeout(); - - protected Stream anyTestDefinitions( - Supplier testInvocation) { - return Stream.of( - testInvocation - .get() - .withInput(startMessage(1), ProtoUtils.inputMessage()) - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - suspensionMessage(1, 2)) - .named("No completions will suspend"), - testInvocation - .get() - .withInput( - startMessage(3), - ProtoUtils.inputMessage(), - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till", "TILL")) - .expectingOutput(combinatorsMessage(2), outputMessage("TILL"), END_MESSAGE) - .named("Only one completion will generate the combinators message"), - testInvocation - .get() - .withInput( - startMessage(3), - ProtoUtils.inputMessage(), - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till") - .setFailure(Util.toProtocolFailure(new IllegalStateException("My error")))) - .expectingOutput( - combinatorsMessage(2), - outputMessage(new IllegalStateException("My error")), - END_MESSAGE) - .named("Only one failure will generate the combinators message"), - testInvocation - .get() - .withInput( - startMessage(3), - ProtoUtils.inputMessage(), - invokeMessage(GREETER_SERVICE_TARGET, "Francesco", "FRANCESCO"), - invokeMessage(GREETER_SERVICE_TARGET, "Till", "TILL")) - .assertingOutput( - msgs -> { - assertThat(msgs).hasSize(3); - - assertThat(msgs) - .element(0, type(Java.CombinatorAwaitableEntryMessage.class)) - .extracting( - Java.CombinatorAwaitableEntryMessage::getEntryIndexList, - list(Integer.class)) - .hasSize(1) - .element(0) - .isIn(1, 2); - - assertThat(msgs) - .element(1) - .isIn(outputMessage("FRANCESCO"), outputMessage("TILL")); - assertThat(msgs).element(2).isEqualTo(END_MESSAGE); - }) - .named("Everything completed will generate the combinators message"), - testInvocation - .get() - .withInput( - startMessage(4), - ProtoUtils.inputMessage(), - invokeMessage(GREETER_SERVICE_TARGET, "Francesco", "FRANCESCO"), - invokeMessage(GREETER_SERVICE_TARGET, "Till", "TILL"), - combinatorsMessage(2)) - .expectingOutput(outputMessage("TILL"), END_MESSAGE) - .named("Replay the combinator"), - testInvocation - .get() - .withInput( - startMessage(1), ProtoUtils.inputMessage(), completionMessage(1, "FRANCESCO")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - combinatorsMessage(1), - outputMessage("FRANCESCO"), - END_MESSAGE) - .named("Complete any asynchronously")); - } - - @Override - public Stream definitions() { - return Stream.concat( - // --- Any combinator - anyTestDefinitions(this::awaitAny), - Stream.of( - // --- Reverse await order - this.reverseAwaitOrder() - .withInput(startMessage(1), ProtoUtils.inputMessage()) - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - suspensionMessage(2)) - .named("None completed"), - this.reverseAwaitOrder() - .withInput( - startMessage(1), - ProtoUtils.inputMessage(), - completionMessage(1, "FRANCESCO"), - completionMessage(2, "TILL")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - setStateMessage("A2", "TILL"), - outputMessage("FRANCESCO-TILL"), - END_MESSAGE) - .named("A1 and A2 completed later"), - this.reverseAwaitOrder() - .withInput( - startMessage(1), - ProtoUtils.inputMessage(), - completionMessage(2, "TILL"), - completionMessage(1, "FRANCESCO")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - setStateMessage("A2", "TILL"), - outputMessage("FRANCESCO-TILL"), - END_MESSAGE) - .named("A2 and A1 completed later"), - this.reverseAwaitOrder() - .withInput(startMessage(1), ProtoUtils.inputMessage(), completionMessage(2, "TILL")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - setStateMessage("A2", "TILL"), - suspensionMessage(1)) - .named("Only A2 completed"), - this.reverseAwaitOrder() - .withInput( - startMessage(1), ProtoUtils.inputMessage(), completionMessage(1, "FRANCESCO")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - suspensionMessage(2)) - .named("Only A1 completed"), - - // --- Await twice the same executable - this.awaitTwiceTheSameAwaitable() - .withInput( - startMessage(1), ProtoUtils.inputMessage(), completionMessage(1, "FRANCESCO")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - outputMessage("FRANCESCO-FRANCESCO"), - END_MESSAGE), - - // --- All combinator - this.awaitAll() - .withInput(startMessage(1), ProtoUtils.inputMessage()) - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - suspensionMessage(1, 2)) - .named("No completions will suspend"), - this.awaitAll() - .withInput( - startMessage(3), - ProtoUtils.inputMessage(), - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till", "TILL")) - .expectingOutput(suspensionMessage(1)) - .named("Only one completion will suspend"), - this.awaitAll() - .withInput( - startMessage(3), - ProtoUtils.inputMessage(), - invokeMessage(GREETER_SERVICE_TARGET, "Francesco", "FRANCESCO"), - invokeMessage(GREETER_SERVICE_TARGET, "Till", "TILL")) - .assertingOutput( - msgs -> { - assertThat(msgs).hasSize(3); - - assertThat(msgs) - .element(0, type(Java.CombinatorAwaitableEntryMessage.class)) - .extracting( - Java.CombinatorAwaitableEntryMessage::getEntryIndexList, - list(Integer.class)) - .containsExactlyInAnyOrder(1, 2); - - assertThat(msgs).element(1).isEqualTo(outputMessage("FRANCESCO-TILL")); - assertThat(msgs).element(2).isEqualTo(END_MESSAGE); - }) - .named("Everything completed will generate the combinators message"), - this.awaitAll() - .withInput( - startMessage(4), - ProtoUtils.inputMessage(), - invokeMessage(GREETER_SERVICE_TARGET, "Francesco", "FRANCESCO"), - invokeMessage(GREETER_SERVICE_TARGET, "Till", "TILL"), - combinatorsMessage(1, 2)) - .expectingOutput(outputMessage("FRANCESCO-TILL"), END_MESSAGE) - .named("Replay the combinator"), - this.awaitAll() - .withInput( - startMessage(1), - ProtoUtils.inputMessage(), - completionMessage(1, "FRANCESCO"), - completionMessage(2, "TILL")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - combinatorsMessage(1, 2), - outputMessage("FRANCESCO-TILL"), - END_MESSAGE) - .named("Complete all asynchronously"), - this.awaitAll() - .withInput( - startMessage(1), - ProtoUtils.inputMessage(), - completionMessage(1, new IllegalStateException("My error"))) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - combinatorsMessage(1), - outputMessage(new IllegalStateException("My error")), - END_MESSAGE) - .named("All fails on first failure"), - this.awaitAll() - .withInput( - startMessage(1), - ProtoUtils.inputMessage(), - completionMessage(1, "FRANCESCO"), - completionMessage(2, new IllegalStateException("My error"))) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(GREETER_SERVICE_TARGET, "Francesco"), - invokeMessage(GREETER_SERVICE_TARGET, "Till"), - combinatorsMessage(1, 2), - outputMessage(new IllegalStateException("My error")), - END_MESSAGE) - .named("All fails on second failure"), - - // --- Compose any with all - this.combineAnyWithAll() - .withInput( - startMessage(6), - ProtoUtils.inputMessage(), - awakeable("1"), - awakeable("2"), - awakeable("3"), - awakeable("4"), - combinatorsMessage(2, 3)) - .expectingOutput(outputMessage("223"), END_MESSAGE), - this.combineAnyWithAll() - .withInput( - startMessage(6), - ProtoUtils.inputMessage(), - awakeable("1"), - awakeable("2"), - awakeable("3"), - awakeable("4"), - combinatorsMessage(3, 2)) - .expectingOutput(outputMessage("233"), END_MESSAGE) - .named("Inverted order"), - - // --- Await Any with index - this.awaitAnyIndex() - .withInput( - startMessage(6), - ProtoUtils.inputMessage(), - awakeable("1"), - awakeable("2"), - awakeable("3"), - awakeable("4"), - combinatorsMessage(1)) - .expectingOutput(outputMessage("0"), END_MESSAGE), - this.awaitAnyIndex() - .withInput( - startMessage(6), - ProtoUtils.inputMessage(), - awakeable("1"), - awakeable("2"), - awakeable("3"), - awakeable("4"), - combinatorsMessage(3, 2)) - .expectingOutput(outputMessage("1"), END_MESSAGE) - .named("Complete all"), - - // --- Compose nested and resolved all should work - this.awaitOnAlreadyResolvedAwaitables() - .withInput( - startMessage(3), ProtoUtils.inputMessage(), awakeable("1"), awakeable("2")) - .assertingOutput( - msgs -> { - assertThat(msgs).hasSize(4); - - assertThat(msgs) - .element(0, type(Java.CombinatorAwaitableEntryMessage.class)) - .extracting( - Java.CombinatorAwaitableEntryMessage::getEntryIndexList, - list(Integer.class)) - .containsExactlyInAnyOrder(1, 2); - - assertThat(msgs).element(1).isEqualTo(combinatorsMessage()); - assertThat(msgs).element(2).isEqualTo(outputMessage("12")); - assertThat(msgs).element(3).isEqualTo(END_MESSAGE); - }), - - // --- Await with timeout - this.awaitWithTimeout() - .withInput( - startMessage(1), ProtoUtils.inputMessage(), completionMessage(1, "FRANCESCO")) - .onlyUnbuffered() - .assertingOutput( - messages -> { - assertThat(messages).hasSize(5); - assertThat(messages) - .element(0) - .isEqualTo(invokeMessage(GREETER_SERVICE_TARGET, "Francesco").build()); - assertThat(messages) - .element(1) - .isInstanceOf(Protocol.SleepEntryMessage.class); - assertThat(messages).element(2).isEqualTo(combinatorsMessage(1)); - assertThat(messages).element(3).isEqualTo(outputMessage("FRANCESCO")); - assertThat(messages).element(4).isEqualTo(END_MESSAGE); - }), - this.awaitWithTimeout() - .withInput( - startMessage(1), - ProtoUtils.inputMessage(), - Protocol.CompletionMessage.newBuilder() - .setEntryIndex(2) - .setEmpty(Empty.getDefaultInstance())) - .onlyUnbuffered() - .assertingOutput( - messages -> { - assertThat(messages).hasSize(5); - assertThat(messages) - .element(0) - .isEqualTo(invokeMessage(GREETER_SERVICE_TARGET, "Francesco").build()); - assertThat(messages) - .element(1) - .isInstanceOf(Protocol.SleepEntryMessage.class); - assertThat(messages).element(2).isEqualTo(combinatorsMessage(2)); - assertThat(messages).element(3).isEqualTo(outputMessage("timeout")); - assertThat(messages).element(4).isEqualTo(END_MESSAGE); - }) - .named("Fires timeout"))); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/EagerStateTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/EagerStateTestSuite.java deleted file mode 100644 index 285d6319..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/EagerStateTestSuite.java +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.*; -import static org.assertj.core.api.AssertionsForClassTypes.entry; - -import com.google.protobuf.MessageLite; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.generated.service.protocol.Protocol.ClearAllStateEntryMessage; -import java.util.Map; -import java.util.stream.Stream; - -public abstract class EagerStateTestSuite implements TestSuite { - - protected abstract TestInvocationBuilder getEmpty(); - - protected abstract TestInvocationBuilder get(); - - protected abstract TestInvocationBuilder getAppendAndGet(); - - protected abstract TestInvocationBuilder getClearAndGet(); - - protected abstract TestInvocationBuilder getClearAllAndGet(); - - protected abstract TestInvocationBuilder listKeys(); - - private static final Map.Entry STATE_FRANCESCO = entry("STATE", "Francesco"); - private static final Map.Entry ANOTHER_STATE_FRANCESCO = - entry("ANOTHER_STATE", "Francesco"); - private static final MessageLite INPUT_TILL = inputMessage("Till"); - private static final MessageLite GET_STATE_FRANCESCO = getStateMessage("STATE", "Francesco"); - private static final MessageLite GET_STATE_FRANCESCO_TILL = - getStateMessage("STATE", "FrancescoTill"); - private static final MessageLite SET_STATE_FRANCESCO_TILL = - setStateMessage("STATE", "FrancescoTill"); - private static final MessageLite OUTPUT_FRANCESCO = outputMessage("Francesco"); - private static final MessageLite OUTPUT_FRANCESCO_TILL = outputMessage("FrancescoTill"); - - @Override - public Stream definitions() { - return Stream.of( - this.getEmpty() - .withInput(startMessage(1).setPartialState(false), INPUT_TILL) - .expectingOutput(getStateEmptyMessage("STATE"), outputMessage("true"), END_MESSAGE) - .named("With complete state"), - this.getEmpty() - .withInput(startMessage(1).setPartialState(true), INPUT_TILL) - .expectingOutput(getStateMessage("STATE"), suspensionMessage(1)) - .named("With partial state"), - this.getEmpty() - .withInput( - startMessage(2).setPartialState(true), INPUT_TILL, getStateEmptyMessage("STATE")) - .expectingOutput(outputMessage("true"), END_MESSAGE) - .named("Resume with partial state"), - this.get() - .withInput( - startMessage(1, "my-greeter", STATE_FRANCESCO).setPartialState(false), INPUT_TILL) - .expectingOutput(GET_STATE_FRANCESCO, OUTPUT_FRANCESCO, END_MESSAGE) - .named("With complete state"), - this.get() - .withInput( - startMessage(1, "my-greeter", STATE_FRANCESCO).setPartialState(true), INPUT_TILL) - .expectingOutput(GET_STATE_FRANCESCO, OUTPUT_FRANCESCO, END_MESSAGE) - .named("With partial state"), - this.get() - .withInput(startMessage(1).setPartialState(true), INPUT_TILL) - .expectingOutput(getStateMessage("STATE"), suspensionMessage(1)) - .named("With partial state without the state entry"), - this.getAppendAndGet() - .withInput(startMessage(1, "my-greeter", STATE_FRANCESCO), INPUT_TILL) - .expectingOutput( - GET_STATE_FRANCESCO, - SET_STATE_FRANCESCO_TILL, - GET_STATE_FRANCESCO_TILL, - OUTPUT_FRANCESCO_TILL, - END_MESSAGE) - .named("With state in the state_map"), - this.getAppendAndGet() - .withInput( - startMessage(1).setPartialState(true), - INPUT_TILL, - completionMessage(1, "Francesco")) - .expectingOutput( - getStateMessage("STATE"), - SET_STATE_FRANCESCO_TILL, - GET_STATE_FRANCESCO_TILL, - OUTPUT_FRANCESCO_TILL, - END_MESSAGE) - .named("With partial state on the first get"), - this.getClearAndGet() - .withInput(startMessage(1, "my-greeter", STATE_FRANCESCO), INPUT_TILL) - .expectingOutput( - GET_STATE_FRANCESCO, - clearStateMessage("STATE"), - getStateEmptyMessage("STATE"), - OUTPUT_FRANCESCO, - END_MESSAGE) - .named("With state in the state_map"), - this.getClearAndGet() - .withInput( - startMessage(1).setPartialState(true), - INPUT_TILL, - completionMessage(1, "Francesco")) - .expectingOutput( - getStateMessage("STATE"), - clearStateMessage("STATE"), - getStateEmptyMessage("STATE"), - OUTPUT_FRANCESCO, - END_MESSAGE) - .named("With partial state on the first get"), - this.getClearAllAndGet() - .withInput( - startMessage(1, "my-greeter", STATE_FRANCESCO, ANOTHER_STATE_FRANCESCO), INPUT_TILL) - .expectingOutput( - GET_STATE_FRANCESCO, - ClearAllStateEntryMessage.getDefaultInstance(), - getStateEmptyMessage("STATE"), - getStateEmptyMessage("ANOTHER_STATE"), - OUTPUT_FRANCESCO, - END_MESSAGE) - .named("With state in the state_map"), - this.getClearAllAndGet() - .withInput( - startMessage(1).setPartialState(true), - INPUT_TILL, - completionMessage(1, STATE_FRANCESCO.getValue())) - .expectingOutput( - getStateMessage("STATE"), - ClearAllStateEntryMessage.getDefaultInstance(), - getStateEmptyMessage("STATE"), - getStateEmptyMessage("ANOTHER_STATE"), - OUTPUT_FRANCESCO, - END_MESSAGE) - .named("With partial state on the first get"), - this.listKeys() - .withInput( - startMessage(1, "my-greeter", STATE_FRANCESCO).setPartialState(true), - INPUT_TILL, - completionMessage(1, stateKeys("a", "b"))) - .expectingOutput( - Protocol.GetStateKeysEntryMessage.getDefaultInstance(), - outputMessage("a,b"), - END_MESSAGE) - .named("With partial state"), - this.listKeys() - .withInput( - startMessage(1, "my-greeter", STATE_FRANCESCO).setPartialState(false), INPUT_TILL) - .expectingOutput( - Protocol.GetStateKeysEntryMessage.newBuilder() - .setValue(stateKeys(STATE_FRANCESCO.getKey())), - outputMessage(STATE_FRANCESCO.getKey()), - END_MESSAGE) - .named("With complete state"), - this.listKeys() - .withInput( - startMessage(2).setPartialState(true), - INPUT_TILL, - Protocol.GetStateKeysEntryMessage.newBuilder().setValue(stateKeys("3", "2", "1"))) - .expectingOutput(outputMessage("3,2,1"), END_MESSAGE) - .named("With replayed list")); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/FlowUtils.java b/sdk-core/src/test/java/dev/restate/sdk/core/FlowUtils.java deleted file mode 100644 index bb41a1b4..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/FlowUtils.java +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Flow; -import java.util.concurrent.atomic.AtomicBoolean; - -public class FlowUtils { - - public static class FutureSubscriber implements Flow.Subscriber { - - private final List messages = new ArrayList<>(); - private final CompletableFuture> future = new CompletableFuture<>(); - - @Override - public void onSubscribe(Flow.Subscription subscription) { - subscription.request(Long.MAX_VALUE); - } - - @Override - public void onNext(T t) { - synchronized (this.messages) { - this.messages.add(t); - } - } - - @Override - public void onError(Throwable throwable) { - this.future.completeExceptionally(throwable); - } - - @Override - public void onComplete() { - this.future.complete(getMessages()); - } - - public CompletableFuture> getFuture() { - return future; - } - - public List getMessages() { - List l; - synchronized (this.messages) { - l = new ArrayList<>(this.messages); - } - return l; - } - } - - public static class BufferedMockPublisher implements Flow.Publisher { - - private final Collection elements; - private final AtomicBoolean subscriptionCancelled; - - public BufferedMockPublisher(Collection elements) { - this.elements = elements; - this.subscriptionCancelled = new AtomicBoolean(false); - } - - @Override - public void subscribe(Flow.Subscriber subscriber) { - subscriber.onSubscribe( - new BufferedMockSubscription<>( - subscriber, new ArrayDeque<>(elements), subscriptionCancelled)); - } - - public boolean isSubscriptionCancelled() { - return subscriptionCancelled.get(); - } - - private static class BufferedMockSubscription implements Flow.Subscription { - - private final Flow.Subscriber subscriber; - private final Queue queue; - private final AtomicBoolean cancelled; - - private BufferedMockSubscription( - Flow.Subscriber subscriber, - Queue queue, - AtomicBoolean subscriptionCancelled) { - this.subscriber = subscriber; - this.queue = queue; - this.cancelled = subscriptionCancelled; - } - - @Override - public void request(long l) { - if (this.cancelled.get()) { - return; - } - while (l != 0 && !this.queue.isEmpty()) { - subscriber.onNext(queue.remove()); - } - - if (this.queue.isEmpty()) { - subscriber.onComplete(); - } - } - - @Override - public void cancel() { - this.cancelled.set(true); - } - } - } - - public static class UnbufferedMockPublisher implements Flow.Publisher { - - private UnbufferedMockSubscription subscription; - - @Override - public void subscribe(Flow.Subscriber subscriber) { - this.subscription = new UnbufferedMockSubscription<>(subscriber); - subscriber.onSubscribe(this.subscription); - } - - public boolean isSubscriptionCancelled() { - return Objects.requireNonNull(this.subscription).cancelled; - } - - public void push(T element) { - Objects.requireNonNull(this.subscription).onPush(element); - } - - public void close() { - Objects.requireNonNull(this.subscription).onClose(); - } - - private static class UnbufferedMockSubscription implements Flow.Subscription { - - private final Flow.Subscriber subscriber; - private final Queue queue; - private boolean publisherClosed = false; - private long request = 0; - private boolean cancelled = false; - - private UnbufferedMockSubscription(Flow.Subscriber subscriber) { - this.subscriber = subscriber; - this.queue = new ArrayDeque<>(); - } - - @Override - public void request(long l) { - if (l == Long.MAX_VALUE) { - this.request = l; - } else { - this.request += l; - // Overflow check - if (this.request < 0) { - this.request = Long.MAX_VALUE; - } - } - this.doProgress(); - } - - @Override - public void cancel() { - this.cancelled = true; - } - - private void onPush(T element) { - this.queue.offer(element); - this.doProgress(); - } - - private void onClose() { - this.publisherClosed = true; - this.doProgress(); - } - - private void doProgress() { - if (this.cancelled) { - return; - } - while (this.request != 0 && !this.queue.isEmpty()) { - this.request--; - subscriber.onNext(queue.remove()); - } - if (this.publisherClosed) { - subscriber.onComplete(); - } - } - } - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/InvocationIdTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/InvocationIdTestSuite.java deleted file mode 100644 index ccb32ca5..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/InvocationIdTestSuite.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.ProtoUtils.END_MESSAGE; -import static dev.restate.sdk.core.ProtoUtils.outputMessage; - -import com.google.protobuf.ByteString; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.core.TestDefinitions.TestDefinition; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import java.util.stream.Stream; - -public abstract class InvocationIdTestSuite implements TestSuite { - - protected abstract TestInvocationBuilder returnInvocationId(); - - @Override - public Stream definitions() { - String debugId = "my-debug-id"; - ByteString id = ByteString.copyFromUtf8(debugId); - - return Stream.of( - returnInvocationId() - .withInput( - Protocol.StartMessage.newBuilder().setDebugId(debugId).setId(id).setKnownEntries(1), - ProtoUtils.inputMessage()) - .onlyUnbuffered() - .expectingOutput(outputMessage(debugId), END_MESSAGE)); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java b/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java deleted file mode 100644 index 3de3267d..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.api.Test; - -public class MessageHeaderTest { - - @Test - void requiresAckFlag() { - assertThat( - new MessageHeader( - MessageType.InvokeEntryMessage, - MessageHeader.DONE_FLAG | MessageHeader.REQUIRES_ACK_FLAG, - 2) - .encode()) - .isEqualTo(0x0C01_8001_0000_0002L); - } - - @Test - void checkProtocolVersion() { - int unknownVersion = Integer.MAX_VALUE & MessageHeader.VERSION_MASK; - assertThatThrownBy( - () -> - MessageHeader.checkProtocolVersion( - new MessageHeader(MessageType.StartMessage, unknownVersion, 0))) - .hasMessage( - "Unsupported protocol version %d, only version %d is supported", - unknownVersion, MessageHeader.SUPPORTED_PROTOCOL_VERSION); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/MockMultiThreaded.java b/sdk-core/src/test/java/dev/restate/sdk/core/MockMultiThreaded.java deleted file mode 100644 index fb802c09..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/MockMultiThreaded.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.protobuf.MessageLite; -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import java.time.Duration; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import org.apache.logging.log4j.ThreadContext; - -public final class MockMultiThreaded implements TestDefinitions.TestExecutor { - - public static final MockMultiThreaded INSTANCE = new MockMultiThreaded(); - - private MockMultiThreaded() {} - - @Override - public boolean buffered() { - return false; - } - - @Override - public void executeTest(TestDefinitions.TestDefinition definition) { - Executor syscallsExecutor = Executors.newSingleThreadExecutor(); - Executor userExecutor = Executors.newSingleThreadExecutor(); - - // Output subscriber buffers all the output messages and provides a completion future - FlowUtils.FutureSubscriber outputSubscriber = new FlowUtils.FutureSubscriber<>(); - - // This test infra supports only components returning one component definition - List componentDefinition = definition.getComponent().definitions(); - assertThat(componentDefinition).size().isEqualTo(1); - - // Prepare server - RestateEndpoint.Builder builder = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.BIDI_STREAM) - .with(componentDefinition.get(0)); - RestateEndpoint server = builder.build(); - - // Start invocation - ResolvedEndpointHandler handler = - server.resolve( - componentDefinition.get(0).getFullyQualifiedServiceName(), - definition.getMethod(), - io.opentelemetry.context.Context.current(), - RestateEndpoint.LoggingContextSetter.THREAD_LOCAL_INSTANCE, - syscallsExecutor, - userExecutor); - - // Create publisher - FlowUtils.UnbufferedMockPublisher inputPublisher = - new FlowUtils.UnbufferedMockPublisher<>(); - - // Wire invocation and start it - syscallsExecutor.execute( - () -> { - handler.output().subscribe(outputSubscriber); - inputPublisher.subscribe(handler.input()); - handler.start(); - }); - - // Pipe entries - for (InvocationFlow.InvocationInput inputEntry : definition.getInput()) { - syscallsExecutor.execute(() -> inputPublisher.push(inputEntry)); - } - // Complete the input publisher - syscallsExecutor.execute(inputPublisher::close); - - // Check completed - assertThat(outputSubscriber.getFuture()) - .succeedsWithin(Duration.ofSeconds(1)) - .satisfies(definition.getOutputAssert()); - assertThat(inputPublisher.isSubscriptionCancelled()).isTrue(); - - // Clean logging - ThreadContext.clearAll(); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/MockSingleThread.java b/sdk-core/src/test/java/dev/restate/sdk/core/MockSingleThread.java deleted file mode 100644 index ddc56b5c..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/MockSingleThread.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.protobuf.MessageLite; -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import dev.restate.sdk.core.TestDefinitions.TestDefinition; -import dev.restate.sdk.core.TestDefinitions.TestExecutor; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import java.time.Duration; -import java.util.List; -import org.apache.logging.log4j.ThreadContext; - -public final class MockSingleThread implements TestExecutor { - - public static final MockSingleThread INSTANCE = new MockSingleThread(); - - private MockSingleThread() {} - - @Override - public boolean buffered() { - return true; - } - - @Override - public void executeTest(TestDefinition definition) { - // Output subscriber buffers all the output messages and provides a completion future - FlowUtils.FutureSubscriber outputSubscriber = new FlowUtils.FutureSubscriber<>(); - - // This test infra supports only components returning one component definition - List componentDefinition = definition.getComponent().definitions(); - assertThat(componentDefinition).size().isEqualTo(1); - - // Prepare server - RestateEndpoint.Builder builder = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.BIDI_STREAM) - .with(componentDefinition.get(0)); - RestateEndpoint server = builder.build(); - - // Start invocation - ResolvedEndpointHandler handler = - server.resolve( - componentDefinition.get(0).getFullyQualifiedServiceName(), - definition.getMethod(), - io.opentelemetry.context.Context.current(), - RestateEndpoint.LoggingContextSetter.THREAD_LOCAL_INSTANCE, - null, - null); - - // Create publisher - FlowUtils.BufferedMockPublisher inputPublisher = - new FlowUtils.BufferedMockPublisher<>(definition.getInput()); - - // Wire invocation - handler.output().subscribe(outputSubscriber); - inputPublisher.subscribe(handler.input()); - - // Start invocation - handler.start(); - - // Check completed - assertThat(outputSubscriber.getFuture()) - .succeedsWithin(Duration.ofDays(1)) - .satisfies(definition.getOutputAssert()); - assertThat(inputPublisher.isSubscriptionCancelled()).isTrue(); - - // Clean logging - ThreadContext.clearAll(); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/OnlyInputAndOutputTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/OnlyInputAndOutputTestSuite.java deleted file mode 100644 index 0cf605e1..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/OnlyInputAndOutputTestSuite.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.TestDefinition; - -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import java.util.stream.Stream; - -public abstract class OnlyInputAndOutputTestSuite implements TestSuite { - - protected abstract TestInvocationBuilder noSyscallsGreeter(); - - @Override - public Stream definitions() { - return Stream.of( - this.noSyscallsGreeter() - .withInput(startMessage(1), inputMessage("Francesco")) - .expectingOutput(outputMessage("Hello Francesco"), END_MESSAGE), - this.noSyscallsGreeter() - .withInput( - startMessage(1), - inputMessage(new TerminalException(TerminalException.Code.CANCELLED))) - .expectingOutput( - outputMessage(new TerminalException(TerminalException.Code.CANCELLED)), - END_MESSAGE)); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/ProtoUtils.java b/sdk-core/src/test/java/dev/restate/sdk/core/ProtoUtils.java deleted file mode 100644 index dcaabd06..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/ProtoUtils.java +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import com.google.protobuf.Empty; -import com.google.protobuf.MessageLite; -import com.google.protobuf.MessageLiteOrBuilder; -import dev.restate.generated.sdk.java.Java; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.generated.service.protocol.Protocol.StartMessage.StateEntry; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.Target; -import dev.restate.sdk.common.TerminalException; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class ProtoUtils { - - /** - * Variant of {@link MessageHeader#fromMessage(MessageLite)} supporting StartMessage and - * CompletionMessage. - */ - public static MessageHeader headerFromMessage(MessageLite msg) { - if (msg instanceof Protocol.StartMessage) { - return new MessageHeader( - MessageType.StartMessage, - MessageHeader.SUPPORTED_PROTOCOL_VERSION, - msg.getSerializedSize()); - } else if (msg instanceof Protocol.CompletionMessage) { - return new MessageHeader(MessageType.CompletionMessage, (short) 0, msg.getSerializedSize()); - } - return MessageHeader.fromMessage(msg); - } - - public static Protocol.StartMessage.Builder startMessage(int entries) { - return Protocol.StartMessage.newBuilder() - .setId(ByteString.copyFromUtf8("abc")) - .setDebugId("abc") - .setKnownEntries(entries) - .setPartialState(true); - } - - public static Protocol.StartMessage.Builder startMessage(int entries, String key) { - return Protocol.StartMessage.newBuilder() - .setId(ByteString.copyFromUtf8("abc")) - .setDebugId("abc") - .setKnownEntries(entries) - .setKey(key) - .setPartialState(true); - } - - @SafeVarargs - public static Protocol.StartMessage.Builder startMessage( - int entries, String key, Map.Entry... stateEntries) { - return startMessage(entries, key) - .addAllStateMap( - Arrays.stream(stateEntries) - .map( - e -> - StateEntry.newBuilder() - .setKey(ByteString.copyFromUtf8(e.getKey())) - .setValue(CoreSerdes.JSON_STRING.serializeToByteString(e.getValue())) - .build()) - .collect(Collectors.toList())); - } - - public static Protocol.CompletionMessage.Builder completionMessage(int index) { - return Protocol.CompletionMessage.newBuilder().setEntryIndex(index); - } - - public static Protocol.CompletionMessage completionMessage( - int index, Serde serde, T value) { - return completionMessage(index).setValue(serde.serializeToByteString(value)).build(); - } - - public static Protocol.CompletionMessage completionMessage(int index, String value) { - return completionMessage(index, CoreSerdes.JSON_STRING, value); - } - - public static Protocol.CompletionMessage completionMessage( - int index, MessageLiteOrBuilder value) { - return completionMessage(index).setValue(build(value).toByteString()).build(); - } - - public static Protocol.CompletionMessage completionMessage(int index, Throwable e) { - return completionMessage(index).setFailure(Util.toProtocolFailure(e)).build(); - } - - public static Protocol.EntryAckMessage ackMessage(int index) { - return Protocol.EntryAckMessage.newBuilder().setEntryIndex(index).build(); - } - - public static Protocol.SuspensionMessage suspensionMessage(Integer... indexes) { - return Protocol.SuspensionMessage.newBuilder().addAllEntryIndexes(List.of(indexes)).build(); - } - - public static Protocol.PollInputStreamEntryMessage inputMessage() { - return Protocol.PollInputStreamEntryMessage.newBuilder().setValue(ByteString.EMPTY).build(); - } - - public static Protocol.PollInputStreamEntryMessage inputMessage(Serde serde, T value) { - return Protocol.PollInputStreamEntryMessage.newBuilder() - .setValue(serde.serializeToByteString(value)) - .build(); - } - - public static Protocol.PollInputStreamEntryMessage inputMessage(String value) { - return inputMessage(CoreSerdes.JSON_STRING, value); - } - - public static Protocol.PollInputStreamEntryMessage inputMessage(int value) { - return inputMessage(CoreSerdes.JSON_INT, value); - } - - public static Protocol.PollInputStreamEntryMessage inputMessage(Throwable error) { - return Protocol.PollInputStreamEntryMessage.newBuilder() - .setFailure(Util.toProtocolFailure(error)) - .build(); - } - - public static Protocol.OutputStreamEntryMessage outputMessage(Serde serde, T value) { - return Protocol.OutputStreamEntryMessage.newBuilder() - .setValue(serde.serializeToByteString(value)) - .build(); - } - - public static Protocol.OutputStreamEntryMessage outputMessage(String value) { - return outputMessage(CoreSerdes.JSON_STRING, value); - } - - public static Protocol.OutputStreamEntryMessage outputMessage(int value) { - return outputMessage(CoreSerdes.JSON_INT, value); - } - - public static Protocol.OutputStreamEntryMessage outputMessage() { - return Protocol.OutputStreamEntryMessage.newBuilder().setValue(ByteString.EMPTY).build(); - } - - public static Protocol.OutputStreamEntryMessage outputMessage( - TerminalException.Code code, String message) { - return Protocol.OutputStreamEntryMessage.newBuilder() - .setFailure(Util.toProtocolFailure(code, message)) - .build(); - } - - public static Protocol.OutputStreamEntryMessage outputMessage(Throwable e) { - return Protocol.OutputStreamEntryMessage.newBuilder() - .setFailure(Util.toProtocolFailure(e)) - .build(); - } - - public static Protocol.GetStateEntryMessage.Builder getStateMessage(String key) { - return Protocol.GetStateEntryMessage.newBuilder().setKey(ByteString.copyFromUtf8(key)); - } - - public static Protocol.GetStateEntryMessage.Builder getStateMessage(String key, Throwable error) { - return getStateMessage(key).setFailure(Util.toProtocolFailure(error)); - } - - public static Protocol.GetStateEntryMessage getStateEmptyMessage(String key) { - return Protocol.GetStateEntryMessage.newBuilder() - .setKey(ByteString.copyFromUtf8(key)) - .setEmpty(Empty.getDefaultInstance()) - .build(); - } - - public static Protocol.GetStateEntryMessage getStateMessage( - String key, Serde serde, T value) { - return getStateMessage(key).setValue(serde.serializeToByteString(value)).build(); - } - - public static Protocol.GetStateEntryMessage getStateMessage(String key, String value) { - return getStateMessage(key, CoreSerdes.JSON_STRING, value); - } - - public static Protocol.SetStateEntryMessage setStateMessage( - String key, Serde serde, T value) { - return Protocol.SetStateEntryMessage.newBuilder() - .setKey(ByteString.copyFromUtf8(key)) - .setValue(serde.serializeToByteString(value)) - .build(); - } - - public static Protocol.SetStateEntryMessage setStateMessage(String key, String value) { - return setStateMessage(key, CoreSerdes.JSON_STRING, value); - } - - public static Protocol.ClearStateEntryMessage clearStateMessage(String key) { - return Protocol.ClearStateEntryMessage.newBuilder() - .setKey(ByteString.copyFromUtf8(key)) - .build(); - } - - public static Protocol.InvokeEntryMessage.Builder invokeMessage(Target target) { - Protocol.InvokeEntryMessage.Builder builder = - Protocol.InvokeEntryMessage.newBuilder() - .setServiceName(target.getComponent()) - .setMethodName(target.getHandler()); - if (target.getKey() != null) { - builder.setKey(target.getKey()); - } - - return builder; - } - - public static Protocol.InvokeEntryMessage.Builder invokeMessage( - Target target, Serde reqSerde, T parameter) { - return invokeMessage(target).setParameter(reqSerde.serializeToByteString(parameter)); - } - - public static Protocol.InvokeEntryMessage invokeMessage( - Target target, Serde reqSerde, T parameter, Serde resSerde, R result) { - return invokeMessage(target, reqSerde, parameter) - .setValue(resSerde.serializeToByteString(result)) - .build(); - } - - public static Protocol.InvokeEntryMessage.Builder invokeMessage(Target target, String parameter) { - return invokeMessage(target, CoreSerdes.JSON_STRING, parameter); - } - - public static Protocol.InvokeEntryMessage invokeMessage( - Target target, String parameter, String result) { - return invokeMessage(target, CoreSerdes.JSON_STRING, parameter, CoreSerdes.JSON_STRING, result); - } - - public static Protocol.AwakeableEntryMessage.Builder awakeable() { - return Protocol.AwakeableEntryMessage.newBuilder(); - } - - public static Protocol.AwakeableEntryMessage awakeable(String value) { - return awakeable().setValue(CoreSerdes.JSON_STRING.serializeToByteString(value)).build(); - } - - public static Java.CombinatorAwaitableEntryMessage combinatorsMessage(Integer... order) { - return Java.CombinatorAwaitableEntryMessage.newBuilder() - .addAllEntryIndex(Arrays.asList(order)) - .build(); - } - - public static Protocol.EndMessage END_MESSAGE = Protocol.EndMessage.getDefaultInstance(); - - public static Target GREETER_SERVICE_TARGET = Target.service("Greeter", "greeter"); - public static Target GREETER_VIRTUAL_OBJECT_TARGET = - Target.virtualObject("Greeter", "Francesco", "greeter"); - - public static Protocol.GetStateKeysEntryMessage.StateKeys.Builder stateKeys(String... keys) { - return Protocol.GetStateKeysEntryMessage.StateKeys.newBuilder() - .addAllKeys(Arrays.stream(keys).map(ByteString::copyFromUtf8).collect(Collectors.toList())); - } - - static MessageLite build(MessageLiteOrBuilder value) { - if (value instanceof MessageLite) { - return (MessageLite) value; - } else { - return ((MessageLite.Builder) value).build(); - } - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/RandomTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/RandomTestSuite.java deleted file mode 100644 index 04c24f35..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/RandomTestSuite.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.AssertUtils.containsOnlyExactErrorMessage; -import static dev.restate.sdk.core.ProtoUtils.*; - -import dev.restate.sdk.core.TestDefinitions.TestDefinition; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import java.util.stream.Stream; - -public abstract class RandomTestSuite implements TestSuite { - - protected abstract TestInvocationBuilder randomShouldBeDeterministic(); - - protected abstract TestInvocationBuilder randomInsideSideEffect(); - - protected abstract int getExpectedInt(long seed); - - @Override - public Stream definitions() { - String debugId = "my-id"; - - return Stream.of( - this.randomShouldBeDeterministic() - .withInput(startMessage(1).setDebugId(debugId), ProtoUtils.inputMessage()) - .expectingOutput( - outputMessage(getExpectedInt(new InvocationIdImpl(debugId).toRandomSeed())), - END_MESSAGE), - this.randomInsideSideEffect() - .withInput(startMessage(1).setDebugId(debugId), ProtoUtils.inputMessage()) - .assertingOutput( - containsOnlyExactErrorMessage( - new IllegalStateException( - "You can't use RestateRandom inside a side effect!")))); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/SideEffectTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/SideEffectTestSuite.java deleted file mode 100644 index 48e58f3f..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/SideEffectTestSuite.java +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.AssertUtils.containsOnlyExactErrorMessage; -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.type; - -import dev.restate.generated.sdk.java.Java; -import dev.restate.sdk.common.CoreSerdes; -import java.util.stream.Stream; - -public abstract class SideEffectTestSuite implements TestDefinitions.TestSuite { - - protected abstract TestInvocationBuilder sideEffect(String sideEffectOutput); - - protected abstract TestInvocationBuilder consecutiveSideEffect(String sideEffectOutput); - - protected abstract TestInvocationBuilder checkContextSwitching(); - - protected abstract TestInvocationBuilder sideEffectGuard(); - - @Override - public Stream definitions() { - return Stream.of( - this.sideEffect("Francesco") - .withInput(startMessage(1), inputMessage("Till")) - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder() - .setValue(CoreSerdes.JSON_STRING.serializeToByteString("Francesco")), - suspensionMessage(1)) - .named("Without optimization suspends"), - this.sideEffect("Francesco") - .withInput(startMessage(1), inputMessage("Till"), ackMessage(1)) - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder() - .setValue(CoreSerdes.JSON_STRING.serializeToByteString("Francesco")), - outputMessage("Hello Francesco"), - END_MESSAGE) - .named("Without optimization and with acks returns"), - this.consecutiveSideEffect("Francesco") - .withInput(startMessage(1), inputMessage("Till")) - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder() - .setValue(CoreSerdes.JSON_STRING.serializeToByteString("Francesco")), - suspensionMessage(1)) - .named("With optimization and without ack on first side effect will suspend"), - this.consecutiveSideEffect("Francesco") - .withInput(startMessage(1), inputMessage("Till"), ackMessage(1)) - .onlyUnbuffered() - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder() - .setValue(CoreSerdes.JSON_STRING.serializeToByteString("Francesco")), - Java.SideEffectEntryMessage.newBuilder() - .setValue(CoreSerdes.JSON_STRING.serializeToByteString("FRANCESCO")), - suspensionMessage(2)) - .named("With optimization and ack on first side effect will suspend"), - this.consecutiveSideEffect("Francesco") - .withInput(startMessage(1), inputMessage("Till"), ackMessage(1), ackMessage(2)) - .onlyUnbuffered() - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder() - .setValue(CoreSerdes.JSON_STRING.serializeToByteString("Francesco")), - Java.SideEffectEntryMessage.newBuilder() - .setValue(CoreSerdes.JSON_STRING.serializeToByteString("FRANCESCO")), - outputMessage("Hello FRANCESCO"), - END_MESSAGE) - .named("With optimization and ack on first and second side effect will resume"), - - // --- Other tests - this.checkContextSwitching() - .withInput(startMessage(1), inputMessage(), ackMessage(1)) - .onlyUnbuffered() - .assertingOutput( - actualOutputMessages -> { - assertThat(actualOutputMessages).hasSize(3); - assertThat(actualOutputMessages) - .element(0) - .asInstanceOf(type(Java.SideEffectEntryMessage.class)) - .returns(true, Java.SideEffectEntryMessage::hasValue); - assertThat(actualOutputMessages).element(1).isEqualTo(outputMessage("Hello")); - assertThat(actualOutputMessages).element(2).isEqualTo(END_MESSAGE); - }), - this.sideEffectGuard() - .withInput(startMessage(1), inputMessage("Till")) - .assertingOutput( - containsOnlyExactErrorMessage(ProtocolException.invalidSideEffectCall()))); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/SleepTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/SleepTestSuite.java deleted file mode 100644 index d1a54d6e..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/SleepTestSuite.java +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.LONG; -import static org.assertj.core.api.InstanceOfAssertFactories.type; - -import com.google.protobuf.Empty; -import com.google.protobuf.MessageLiteOrBuilder; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.common.TerminalException; -import java.time.Instant; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -public abstract class SleepTestSuite implements TestDefinitions.TestSuite { - - Long startTime = System.currentTimeMillis(); - - protected abstract TestInvocationBuilder sleepGreeter(); - - protected abstract TestInvocationBuilder manySleeps(); - - @Override - public Stream definitions() { - return Stream.of( - this.sleepGreeter() - .withInput(startMessage(1), inputMessage("Till")) - .assertingOutput( - messageLites -> { - assertThat(messageLites) - .element(0) - .asInstanceOf(type(Protocol.SleepEntryMessage.class)) - .extracting(Protocol.SleepEntryMessage::getWakeUpTime, LONG) - .isGreaterThanOrEqualTo(startTime + 1000) - .isLessThanOrEqualTo(Instant.now().toEpochMilli() + 1000); - - assertThat(messageLites) - .element(1) - .isInstanceOf(Protocol.SuspensionMessage.class); - }) - .named("Sleep 1000 ms not completed"), - this.sleepGreeter() - .withInput( - startMessage(2), - inputMessage("Till"), - Protocol.SleepEntryMessage.newBuilder() - .setWakeUpTime(Instant.now().toEpochMilli()) - .setEmpty(Empty.getDefaultInstance()) - .build()) - .expectingOutput(outputMessage("Hello"), END_MESSAGE) - .named("Sleep 1000 ms sleep completed"), - this.sleepGreeter() - .withInput( - startMessage(2), - inputMessage("Till"), - Protocol.SleepEntryMessage.newBuilder() - .setWakeUpTime(Instant.now().toEpochMilli()) - .build()) - .expectingOutput(suspensionMessage(1)) - .named("Sleep 1000 ms still sleeping"), - this.manySleeps() - .withInput( - Stream.concat( - Stream.of(startMessage(11), inputMessage("Till")), - IntStream.rangeClosed(1, 10) - .mapToObj( - i -> - (i % 3 == 0) - ? Protocol.SleepEntryMessage.newBuilder() - .setWakeUpTime(Instant.now().toEpochMilli()) - .setEmpty(Empty.getDefaultInstance()) - .build() - : Protocol.SleepEntryMessage.newBuilder() - .setWakeUpTime(Instant.now().toEpochMilli()) - .build())) - .toArray(MessageLiteOrBuilder[]::new)) - .expectingOutput(suspensionMessage(1, 2, 4, 5, 7, 8, 10)) - .named("Sleep 1000 ms sleep completed"), - this.sleepGreeter() - .withInput( - startMessage(2), - inputMessage("Till"), - Protocol.SleepEntryMessage.newBuilder() - .setWakeUpTime(Instant.now().toEpochMilli()) - .setFailure( - Util.toProtocolFailure(TerminalException.Code.CANCELLED, "canceled")) - .build()) - .expectingOutput( - outputMessage(TerminalException.Code.CANCELLED, "canceled"), END_MESSAGE) - .named("Failed sleep"), - this.sleepGreeter() - .withInput( - startMessage(1), - inputMessage("Till"), - completionMessage( - 1, new TerminalException(TerminalException.Code.CANCELLED, "canceled"))) - .assertingOutput( - messageLites -> { - assertThat(messageLites) - .element(0) - .isInstanceOf(Protocol.SleepEntryMessage.class); - assertThat(messageLites) - .element(1) - .isEqualTo(outputMessage(TerminalException.Code.CANCELLED, "canceled")); - assertThat(messageLites).element(2).isEqualTo(END_MESSAGE); - }) - .named("Failing sleep")); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/StateMachineFailuresTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/StateMachineFailuresTestSuite.java deleted file mode 100644 index 7a4c6cf4..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/StateMachineFailuresTestSuite.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.AssertUtils.errorMessageStartingWith; -import static dev.restate.sdk.core.AssertUtils.protocolExceptionErrorMessage; -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import static org.assertj.core.api.Assertions.assertThat; - -import dev.restate.generated.sdk.java.Java; -import dev.restate.sdk.common.Serde; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; -import org.assertj.core.api.Assertions; - -public abstract class StateMachineFailuresTestSuite implements TestDefinitions.TestSuite { - - protected abstract TestInvocationBuilder getState(AtomicInteger nonTerminalExceptionsSeen); - - protected abstract TestInvocationBuilder sideEffectFailure(Serde serde); - - private static final Serde FAILING_SERIALIZATION_INTEGER_TYPE_TAG = - Serde.using( - i -> { - throw new IllegalStateException("Cannot serialize integer"); - }, - b -> Integer.parseInt(new String(b, StandardCharsets.UTF_8))); - - private static final Serde FAILING_DESERIALIZATION_INTEGER_TYPE_TAG = - Serde.using( - i -> Integer.toString(i).getBytes(StandardCharsets.UTF_8), - b -> { - throw new IllegalStateException("Cannot deserialize integer"); - }); - - @Override - public Stream definitions() { - AtomicInteger nonTerminalExceptionsSeenTest1 = new AtomicInteger(); - AtomicInteger nonTerminalExceptionsSeenTest2 = new AtomicInteger(); - - return Stream.of( - this.getState(nonTerminalExceptionsSeenTest1) - .withInput(startMessage(2), inputMessage("Till"), getStateMessage("Something")) - .assertingOutput( - msgs -> { - Assertions.assertThat(msgs) - .satisfiesExactly( - protocolExceptionErrorMessage(ProtocolException.JOURNAL_MISMATCH_CODE)); - assertThat(nonTerminalExceptionsSeenTest1).hasValue(0); - }) - .named("Protocol Exception"), - this.getState(nonTerminalExceptionsSeenTest2) - .withInput( - startMessage(2), - inputMessage("Till"), - getStateMessage("STATE", "This is not an integer")) - .assertingOutput( - msgs -> { - Assertions.assertThat(msgs) - .satisfiesExactly( - errorMessageStartingWith(NumberFormatException.class.getCanonicalName())); - assertThat(nonTerminalExceptionsSeenTest2).hasValue(0); - }) - .named("Serde error"), - this.sideEffectFailure(FAILING_SERIALIZATION_INTEGER_TYPE_TAG) - .withInput(startMessage(1), inputMessage("Till")) - .assertingOutput( - AssertUtils.containsOnly( - errorMessageStartingWith(IllegalStateException.class.getCanonicalName()))) - .named("Serde serialization error"), - this.sideEffectFailure(FAILING_DESERIALIZATION_INTEGER_TYPE_TAG) - .withInput( - startMessage(2), inputMessage("Till"), Java.SideEffectEntryMessage.newBuilder()) - .assertingOutput( - AssertUtils.containsOnly( - errorMessageStartingWith(IllegalStateException.class.getCanonicalName()))) - .named("Serde deserialization error")); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/StateTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/StateTestSuite.java deleted file mode 100644 index 67d83922..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/StateTestSuite.java +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.AssertUtils.containsOnlyExactErrorMessage; -import static dev.restate.sdk.core.ProtoUtils.*; -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.protobuf.Empty; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.util.stream.Stream; - -public abstract class StateTestSuite implements TestDefinitions.TestSuite { - - protected abstract TestInvocationBuilder getState(); - - protected abstract TestInvocationBuilder getAndSetState(); - - protected abstract TestInvocationBuilder setNullState(); - - @Override - public Stream definitions() { - return Stream.of( - this.getState() - .withInput(startMessage(2), inputMessage("Till"), getStateMessage("STATE", "Francesco")) - .expectingOutput(outputMessage("Hello Francesco"), END_MESSAGE) - .named("With GetStateEntry already completed"), - this.getState() - .withInput( - startMessage(2), - inputMessage("Till"), - getStateMessage("STATE").setEmpty(Empty.getDefaultInstance())) - .expectingOutput(outputMessage("Hello Unknown"), END_MESSAGE) - .named("With GetStateEntry already completed empty"), - this.getState() - .withInput(startMessage(1), inputMessage("Till")) - .expectingOutput(getStateMessage("STATE"), suspensionMessage(1)) - .named("Without GetStateEntry"), - this.getState() - .withInput(startMessage(2), inputMessage("Till"), getStateMessage("STATE")) - .expectingOutput(suspensionMessage(1)) - .named("With GetStateEntry not completed"), - this.getState() - .withInput( - startMessage(2), - inputMessage("Till"), - getStateMessage("STATE"), - completionMessage(1, "Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Hello Francesco"), END_MESSAGE) - .named("With GetStateEntry and completed with later CompletionFrame"), - this.getState() - .withInput(startMessage(1), inputMessage("Till"), completionMessage(1, "Francesco")) - .onlyUnbuffered() - .expectingOutput( - getStateMessage("STATE"), outputMessage("Hello Francesco"), END_MESSAGE) - .named("Without GetStateEntry and completed with later CompletionFrame"), - this.getState() - .withInput( - startMessage(2), - inputMessage("Till"), - getStateMessage("STATE", new TerminalException(TerminalException.Code.CANCELLED))) - .expectingOutput( - outputMessage(new TerminalException(TerminalException.Code.CANCELLED)), END_MESSAGE) - .named("Failed GetStateEntry"), - this.getState() - .withInput( - startMessage(1), - inputMessage("Till"), - completionMessage(1, new TerminalException(TerminalException.Code.CANCELLED))) - .assertingOutput( - messageLites -> { - assertThat(messageLites) - .element(0) - .isInstanceOf(Protocol.GetStateEntryMessage.class); - assertThat(messageLites) - .element(1) - .isEqualTo( - outputMessage(new TerminalException(TerminalException.Code.CANCELLED))); - assertThat(messageLites).element(2).isEqualTo(END_MESSAGE); - }) - .named("Failing GetStateEntry"), - this.getAndSetState() - .withInput( - startMessage(3), - inputMessage("Till"), - getStateMessage("STATE", "Francesco"), - setStateMessage("STATE", "Till")) - .expectingOutput(outputMessage("Hello Francesco"), END_MESSAGE) - .named("With GetState and SetState"), - this.getAndSetState() - .withInput(startMessage(2), inputMessage("Till"), getStateMessage("STATE", "Francesco")) - .expectingOutput( - setStateMessage("STATE", "Till"), outputMessage("Hello Francesco"), END_MESSAGE) - .named("With GetState already completed"), - this.getAndSetState() - .withInput(startMessage(1), inputMessage("Till"), completionMessage(1, "Francesco")) - .onlyUnbuffered() - .expectingOutput( - getStateMessage("STATE"), - setStateMessage("STATE", "Till"), - outputMessage("Hello Francesco"), - END_MESSAGE) - .named("With GetState completed later"), - this.setNullState() - .withInput(startMessage(1), inputMessage("Till")) - .assertingOutput(containsOnlyExactErrorMessage(new NullPointerException()))); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/TestDefinitions.java b/sdk-core/src/test/java/dev/restate/sdk/core/TestDefinitions.java deleted file mode 100644 index bcd6799e..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/TestDefinitions.java +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.ProtoUtils.headerFromMessage; -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.protobuf.MessageLite; -import com.google.protobuf.MessageLiteOrBuilder; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.common.BindableComponent; -import java.util.*; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.jspecify.annotations.Nullable; - -public final class TestDefinitions { - - private TestDefinitions() {} - - public interface TestDefinition { - BindableComponent getComponent(); - - String getMethod(); - - boolean isOnlyUnbuffered(); - - List getInput(); - - Consumer> getOutputAssert(); - - String getTestCaseName(); - - default boolean isValid() { - return this.getInvalidReason() == null; - } - - @Nullable String getInvalidReason(); - } - - public interface TestSuite { - Stream definitions(); - } - - public interface TestExecutor { - boolean buffered(); - - void executeTest(TestDefinition definition); - } - - public static TestInvocationBuilder testInvocation(Supplier svcSupplier, String handler) { - Object component; - try { - component = svcSupplier.get(); - } catch (UnsupportedOperationException e) { - return new TestInvocationBuilder(Objects.requireNonNull(e.getMessage())); - } - return testInvocation(component, handler); - } - - public static TestInvocationBuilder testInvocation(Object component, String handler) { - if (component instanceof BindableComponent) { - return new TestInvocationBuilder((BindableComponent) component, handler); - } - - // In case it's code generated, discover the adapter - BindableComponent bindableComponent = - RestateEndpoint.discoverAdapter(component).adapt(component); - return new TestInvocationBuilder(bindableComponent, handler); - } - - public static TestInvocationBuilder unsupported(String reason) { - return new TestInvocationBuilder(Objects.requireNonNull(reason)); - } - - public static class TestInvocationBuilder { - protected final @Nullable BindableComponent component; - protected final @Nullable String handler; - protected final @Nullable String invalidReason; - - TestInvocationBuilder(BindableComponent component, String handler) { - this.component = component; - this.handler = handler; - - this.invalidReason = null; - } - - TestInvocationBuilder(String invalidReason) { - this.component = null; - this.handler = null; - - this.invalidReason = invalidReason; - } - - public WithInputBuilder withInput(MessageLiteOrBuilder... messages) { - if (invalidReason != null) { - return new WithInputBuilder(invalidReason); - } - - return new WithInputBuilder( - component, - handler, - Arrays.stream(messages) - .map( - msgOrBuilder -> { - MessageLite msg = ProtoUtils.build(msgOrBuilder); - return InvocationFlow.InvocationInput.of(headerFromMessage(msg), msg); - }) - .collect(Collectors.toList())); - } - } - - public static class WithInputBuilder extends TestInvocationBuilder { - private final List input; - private boolean onlyUnbuffered = false; - - WithInputBuilder(@Nullable String invalidReason) { - super(invalidReason); - this.input = Collections.emptyList(); - } - - WithInputBuilder( - BindableComponent component, String method, List input) { - super(component, method); - this.input = new ArrayList<>(input); - } - - @Override - public WithInputBuilder withInput(MessageLiteOrBuilder... messages) { - if (this.invalidReason == null) { - this.input.addAll( - Arrays.stream(messages) - .map( - msgOrBuilder -> { - MessageLite msg = ProtoUtils.build(msgOrBuilder); - return InvocationFlow.InvocationInput.of(headerFromMessage(msg), msg); - }) - .collect(Collectors.toList())); - } - return this; - } - - public WithInputBuilder onlyUnbuffered() { - this.onlyUnbuffered = true; - return this; - } - - public ExpectingOutputMessages expectingOutput(MessageLiteOrBuilder... messages) { - List builtMessages = - Arrays.stream(messages).map(ProtoUtils::build).collect(Collectors.toList()); - return assertingOutput(actual -> assertThat(actual).asList().isEqualTo(builtMessages)); - } - - public ExpectingOutputMessages assertingOutput(Consumer> messages) { - return new ExpectingOutputMessages( - component, invalidReason, handler, input, onlyUnbuffered, messages); - } - } - - public abstract static class BaseTestDefinition implements TestDefinition { - protected final @Nullable BindableComponent component; - protected final @Nullable String invalidReason; - protected final String method; - protected final List input; - protected final boolean onlyUnbuffered; - protected final String named; - - private BaseTestDefinition( - @Nullable BindableComponent component, - @Nullable String invalidReason, - String method, - List input, - boolean onlyUnbuffered, - String named) { - this.component = component; - this.invalidReason = invalidReason; - this.method = method; - this.input = input; - this.onlyUnbuffered = onlyUnbuffered; - this.named = named; - } - - @Override - public BindableComponent getComponent() { - return Objects.requireNonNull(component); - } - - @Override - public String getMethod() { - return method; - } - - @Override - public List getInput() { - return input; - } - - @Override - public boolean isOnlyUnbuffered() { - return onlyUnbuffered; - } - - @Override - public String getTestCaseName() { - return this.named; - } - - @Override - @Nullable - public String getInvalidReason() { - return invalidReason; - } - } - - public static class ExpectingOutputMessages extends BaseTestDefinition { - private final Consumer> messagesAssert; - - private ExpectingOutputMessages( - @Nullable BindableComponent component, - @Nullable String invalidReason, - String method, - List input, - boolean onlyUnbuffered, - Consumer> messagesAssert) { - super( - component, - invalidReason, - method, - input, - onlyUnbuffered, - component != null - ? component.definitions().get(0).getFullyQualifiedServiceName() - : "Unknown"); - this.messagesAssert = messagesAssert; - } - - ExpectingOutputMessages( - @Nullable BindableComponent component, - @Nullable String invalidReason, - String method, - List input, - boolean onlyUnbuffered, - Consumer> messagesAssert, - String named) { - super(component, invalidReason, method, input, onlyUnbuffered, named); - this.messagesAssert = messagesAssert; - } - - public ExpectingOutputMessages named(String name) { - return new ExpectingOutputMessages( - component, - invalidReason, - method, - input, - onlyUnbuffered, - messagesAssert, - this.named + ": " + name); - } - - @Override - public Consumer> getOutputAssert() { - return outputMessages -> { - messagesAssert.accept(outputMessages); - - // Assert the last message is either an OutputStreamEntry or a SuspensionMessage - assertThat(outputMessages) - .last() - .isNotNull() - .isInstanceOfAny( - Protocol.ErrorMessage.class, - Protocol.SuspensionMessage.class, - Protocol.EndMessage.class); - }; - } - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/TestRunner.java b/sdk-core/src/test/java/dev/restate/sdk/core/TestRunner.java deleted file mode 100644 index ea527815..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/TestRunner.java +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.entry; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import dev.restate.sdk.core.TestDefinitions.TestDefinition; -import dev.restate.sdk.core.TestDefinitions.TestExecutor; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import java.lang.reflect.Method; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.*; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.opentest4j.TestAbortedException; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public abstract class TestRunner { - - protected abstract Stream executors(); - - protected abstract Stream definitions(); - - final Stream source() { - List executors = executors().collect(Collectors.toList()); - - return definitions() - .flatMap(ts -> ts.definitions().map(def -> entry(ts.getClass().getName(), def))) - .flatMap( - entry -> - executors.stream() - .filter( - executor -> !entry.getValue().isOnlyUnbuffered() || !executor.buffered()) - .map( - executor -> - arguments( - "[" - + executor.getClass().getSimpleName() - + "][" - + entry.getKey() - + "] " - + entry.getValue().getTestCaseName(), - executor, - entry.getValue()))); - } - - private static class DisableInvalidTestDefinition implements InvocationInterceptor { - - @Override - public void interceptTestTemplateMethod( - Invocation invocation, - ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext) - throws Throwable { - Method testMethod = extensionContext.getRequiredTestMethod(); - List arguments = invocationContext.getArguments(); - if (arguments.isEmpty()) { - throw new ExtensionConfigurationException( - format( - "Can't disable based on arguments, because method %s had no parameters.", - testMethod.getName())); - } - - Object maybeTestDefinition = arguments.get(2); - if (!(maybeTestDefinition instanceof TestDefinition)) { - throw new ExtensionConfigurationException( - format( - "Expected second argument to be a TestDefinition, but is %s.", - maybeTestDefinition)); - } - - if (!((TestDefinition) maybeTestDefinition).isValid()) { - throw new TestAbortedException( - "Disabled test definition: " - + ((TestDefinition) maybeTestDefinition).getInvalidReason()); - } - invocation.proceed(); - } - } - - @ExtendWith(DisableInvalidTestDefinition.class) - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("source") - @Execution(ExecutionMode.CONCURRENT) - void executeTest(String testName, TestExecutor executor, TestDefinition definition) { - executor.executeTest(definition); - } -} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/UserFailuresTestSuite.java b/sdk-core/src/test/java/dev/restate/sdk/core/UserFailuresTestSuite.java deleted file mode 100644 index a8950576..00000000 --- a/sdk-core/src/test/java/dev/restate/sdk/core/UserFailuresTestSuite.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import static dev.restate.sdk.core.AssertUtils.containsOnlyExactErrorMessage; -import static dev.restate.sdk.core.AssertUtils.exactErrorMessage; -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.*; -import static org.assertj.core.api.Assertions.assertThat; - -import dev.restate.generated.sdk.java.Java; -import dev.restate.sdk.common.TerminalException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; - -public abstract class UserFailuresTestSuite implements TestSuite { - - public static final String MY_ERROR = "my error"; - - public static final String WHATEVER = "Whatever"; - - protected abstract TestInvocationBuilder throwIllegalStateException(); - - protected abstract TestInvocationBuilder sideEffectThrowIllegalStateException( - AtomicInteger nonTerminalExceptionsSeen); - - protected abstract TestInvocationBuilder throwTerminalException( - TerminalException.Code code, String message); - - protected abstract TestInvocationBuilder sideEffectThrowTerminalException( - TerminalException.Code code, String message); - - @Override - public Stream definitions() { - AtomicInteger nonTerminalExceptionsSeen = new AtomicInteger(); - - return Stream.of( - // Cases returning ErrorMessage - this.throwIllegalStateException() - .withInput(startMessage(1), inputMessage()) - .assertingOutput(containsOnlyExactErrorMessage(new IllegalStateException("Whatever"))), - this.sideEffectThrowIllegalStateException(nonTerminalExceptionsSeen) - .withInput(startMessage(1), inputMessage()) - .assertingOutput( - msgs -> { - assertThat(msgs) - .satisfiesExactly(exactErrorMessage(new IllegalStateException("Whatever"))); - - // Check the counter has not been incremented - assertThat(nonTerminalExceptionsSeen).hasValue(0); - }), - - // Cases completing the invocation with OutputStreamEntry.failure - this.throwTerminalException(TerminalException.Code.INTERNAL, MY_ERROR) - .withInput(startMessage(1), inputMessage()) - .expectingOutput(outputMessage(TerminalException.Code.INTERNAL, MY_ERROR), END_MESSAGE) - .named("With internal error"), - this.throwTerminalException(TerminalException.Code.UNKNOWN, WHATEVER) - .withInput(startMessage(1), inputMessage()) - .expectingOutput(outputMessage(TerminalException.Code.UNKNOWN, WHATEVER), END_MESSAGE) - .named("With unknown error"), - this.sideEffectThrowTerminalException(TerminalException.Code.INTERNAL, MY_ERROR) - .withInput(startMessage(1), inputMessage(), ackMessage(1)) - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder() - .setFailure(Util.toProtocolFailure(TerminalException.Code.INTERNAL, MY_ERROR)), - outputMessage(TerminalException.Code.INTERNAL, MY_ERROR), - END_MESSAGE) - .named("With internal error"), - this.sideEffectThrowTerminalException(TerminalException.Code.UNKNOWN, WHATEVER) - .withInput(startMessage(1), inputMessage(), ackMessage(1)) - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder() - .setFailure(Util.toProtocolFailure(TerminalException.Code.UNKNOWN, WHATEVER)), - outputMessage(TerminalException.Code.UNKNOWN, WHATEVER), - END_MESSAGE) - .named("With unknown error")); - } -} diff --git a/sdk-core/src/test/resources/junit-platform.properties b/sdk-core/src/test/resources/junit-platform.properties deleted file mode 100644 index 3e799af0..00000000 --- a/sdk-core/src/test/resources/junit-platform.properties +++ /dev/null @@ -1,3 +0,0 @@ -junit.jupiter.execution.parallel.enabled = true -junit.jupiter.execution.parallel.config.strategy = dynamic -junit.jupiter.execution.parallel.mode.default = same_thread \ No newline at end of file diff --git a/sdk-core/src/test/resources/log4j2.properties b/sdk-core/src/test/resources/log4j2.properties deleted file mode 100644 index 5fd081b5..00000000 --- a/sdk-core/src/test/resources/log4j2.properties +++ /dev/null @@ -1,8 +0,0 @@ -rootLogger.level = TRACE -rootLogger.appenderRef.testlogger.ref = TestLogger - -appender.testlogger.name = TestLogger -appender.testlogger.type = CONSOLE -appender.testlogger.target = SYSTEM_ERR -appender.testlogger.layout.type = PatternLayout -appender.testlogger.layout.pattern = %-4r [%t] %-5p %X %c:%L - %m%n \ No newline at end of file diff --git a/sdk-http-vertx/build.gradle.kts b/sdk-http-vertx/build.gradle.kts deleted file mode 100644 index 67b619a6..00000000 --- a/sdk-http-vertx/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -plugins { - `java-library` - kotlin("jvm") - `library-publishing-conventions` -} - -description = "Restate SDK HTTP implementation based on Vert.x" - -dependencies { - compileOnly(coreLibs.jspecify) - - api(project(":sdk-common")) - implementation(project(":sdk-core")) - - // Vert.x - implementation(platform(vertxLibs.vertx.bom)) - implementation(vertxLibs.vertx.core) - - // Jackson (we need it for the manifest) - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.databind) - - // Observability - implementation(platform(coreLibs.opentelemetry.bom)) - implementation(coreLibs.opentelemetry.api) - implementation(coreLibs.log4j.api) - implementation("io.reactiverse:reactiverse-contextual-logging:1.1.2") - - // Testing - testImplementation(project(":sdk-api")) - testImplementation(project(":sdk-serde-jackson")) - testAnnotationProcessor(project(":sdk-api-gen")) - testImplementation(project(":sdk-api-kotlin")) - testImplementation(project(":sdk-core", "testArchive")) - testImplementation(project(":sdk-api", "testArchive")) - testImplementation(project(":sdk-api-gen", "testArchive")) - testImplementation(project(":sdk-api-kotlin", "testArchive")) - testImplementation(project(":sdk-api-kotlin-gen", "testArchive")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(vertxLibs.vertx.junit5) - - testImplementation(coreLibs.protobuf.java) - testImplementation(coreLibs.protobuf.kotlin) - testImplementation(coreLibs.log4j.core) - - testImplementation(kotlinLibs.kotlinx.coroutines) - testImplementation(vertxLibs.vertx.kotlin.coroutines) -} diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpRequestFlowAdapter.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpRequestFlowAdapter.java deleted file mode 100644 index c581c79d..00000000 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpRequestFlowAdapter.java +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx; - -import dev.restate.sdk.core.InvocationFlow; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpServerRequest; -import java.util.concurrent.Flow; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -class HttpRequestFlowAdapter implements InvocationFlow.InvocationInputPublisher { - - private static final Logger LOG = LogManager.getLogger(HttpRequestFlowAdapter.class); - - private final HttpServerRequest httpServerRequest; - private final MessageDecoder decoder; - - private Flow.Subscriber inputMessagesSubscriber; - private long subscriberRequest = 0; - - HttpRequestFlowAdapter(HttpServerRequest httpServerRequest) { - this.httpServerRequest = httpServerRequest; - - this.decoder = new MessageDecoder(); - } - - @Override - public void subscribe(Flow.Subscriber subscriber) { - this.inputMessagesSubscriber = subscriber; - this.inputMessagesSubscriber.onSubscribe( - new Flow.Subscription() { - @Override - public void request(long l) { - handleSubscriptionRequest(l); - } - - @Override - public void cancel() { - closeRequest(); - } - }); - - // Set request handlers - this.httpServerRequest.handler(this::handleIncomingBuffer); - this.httpServerRequest.exceptionHandler(this::handleRequestFailure); - this.httpServerRequest.endHandler(this::handleRequestEnd); - } - - private void closeRequest() { - if (!this.httpServerRequest.isEnded()) { - this.httpServerRequest.end(); - } - } - - private void handleSubscriptionRequest(long l) { - if (l == Long.MAX_VALUE) { - this.subscriberRequest = l; - } else { - this.subscriberRequest += l; - // Overflow check - if (this.subscriberRequest < 0) { - this.subscriberRequest = Long.MAX_VALUE; - } - } - - tryProgress(); - } - - private void handleIncomingBuffer(Buffer buffer) { - this.decoder.offer(buffer); - - tryProgress(); - } - - private void handleRequestFailure(Throwable e) { - LOG.trace("Request error", e); - this.inputMessagesSubscriber.onError(e); - } - - private void handleRequestEnd(Void v) { - LOG.trace("Request end"); - this.inputMessagesSubscriber.onComplete(); - this.inputMessagesSubscriber = null; - } - - private void tryProgress() { - while (this.subscriberRequest > 0) { - InvocationFlow.InvocationInput input; - try { - input = this.decoder.poll(); - } catch (RuntimeException e) { - inputMessagesSubscriber.onError(e); - return; - } - if (input == null) { - return; - } - LOG.trace("Received input " + input); - this.subscriberRequest--; - inputMessagesSubscriber.onNext(input); - } - } -} diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpResponseFlowAdapter.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpResponseFlowAdapter.java deleted file mode 100644 index e0be0b6a..00000000 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpResponseFlowAdapter.java +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx; - -import com.google.protobuf.MessageLite; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.core.InvocationFlow; -import dev.restate.sdk.core.Util; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpServerResponse; -import java.util.concurrent.Flow; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -class HttpResponseFlowAdapter implements InvocationFlow.InvocationOutputSubscriber { - - private static final Logger LOG = LogManager.getLogger(HttpResponseFlowAdapter.class); - - private final HttpServerResponse httpServerResponse; - - private Flow.Subscription outputSubscription; - - HttpResponseFlowAdapter(HttpServerResponse httpServerResponse) { - this.httpServerResponse = httpServerResponse; - - this.httpServerResponse.exceptionHandler(this::propagateWireFailure); - } - - @Override - public void onSubscribe(Flow.Subscription subscription) { - this.outputSubscription = subscription; - this.outputSubscription.request(Long.MAX_VALUE); - } - - @Override - public void onNext(MessageLite messageLite) { - write(messageLite); - } - - @Override - public void onError(Throwable throwable) { - propagatePublisherFailure(throwable); - } - - @Override - public void onComplete() { - endResponse(); - } - - // --- Private operations - - private void write(MessageLite message) { - if (this.httpServerResponse.closed()) { - cancelSubscription(); - return; - } - - LOG.trace("Writing response message " + message); - - // Could be pooled - Buffer buffer = Buffer.buffer(MessageEncoder.encodeLength(message)); - MessageEncoder.encode(buffer, message); - - // If HTTP HEADERS frame have not been sent, Vert.x will send them - this.httpServerResponse.write(buffer); - } - - private void propagateWireFailure(Throwable e) { - LOG.warn("Error from wire", e); - this.endResponse(); - } - - private void propagatePublisherFailure(Throwable e) { - if (!httpServerResponse.headWritten()) { - // Try to write the failure in the head - Util.findProtocolException(e) - .ifPresentOrElse( - pe -> - // TODO which status codes we need to map here? - httpServerResponse.setStatusCode( - pe.getFailureCode() == TerminalException.Code.NOT_FOUND.value() ? 404 : 500), - () -> httpServerResponse.setStatusCode(500)); - } - LOG.warn("Error from publisher", e); - this.endResponse(); - } - - private void endResponse() { - LOG.trace("Closing response"); - if (!this.httpServerResponse.ended()) { - this.httpServerResponse.end(); - } - cancelSubscription(); - } - - private void cancelSubscription() { - LOG.trace("Cancelling subscription"); - if (this.outputSubscription != null) { - Flow.Subscription outputSubscription = this.outputSubscription; - this.outputSubscription = null; - outputSubscription.cancel(); - } - } -} diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/MessageDecoder.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/MessageDecoder.java deleted file mode 100644 index 19a7b631..00000000 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/MessageDecoder.java +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx; - -import com.google.protobuf.InvalidProtocolBufferException; -import dev.restate.sdk.core.InvocationFlow; -import dev.restate.sdk.core.MessageHeader; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.vertx.core.buffer.Buffer; -import java.util.ArrayDeque; -import java.util.Queue; - -class MessageDecoder { - - private enum State { - WAITING_HEADER, - WAITING_PAYLOAD, - FAILED - } - - private final Queue parsedMessages; - private final ByteBuf internalBuffer; - - private State state; - private MessageHeader lastParsedMessageHeader; - private RuntimeException lastParsingFailure; - - MessageDecoder() { - this.parsedMessages = new ArrayDeque<>(); - this.internalBuffer = Unpooled.compositeBuffer(); - - this.state = State.WAITING_HEADER; - this.lastParsedMessageHeader = null; - this.lastParsingFailure = null; - } - - InvocationFlow.InvocationInput poll() { - if (this.state == State.FAILED) { - throw lastParsingFailure; - } - return this.parsedMessages.poll(); - } - - void offer(Buffer buffer) { - if (this.state != State.FAILED) { - this.internalBuffer.writeBytes(buffer.getByteBuf()); - this.tryConsumeInternalBuffer(); - } - } - - // -- Internal methods to handle decoding - - private void tryConsumeInternalBuffer() { - while (this.state != State.FAILED && this.internalBuffer.readableBytes() >= wantBytes()) { - if (state == State.WAITING_HEADER) { - try { - this.lastParsedMessageHeader = MessageHeader.parse(this.internalBuffer.readLong()); - this.state = State.WAITING_PAYLOAD; - } catch (RuntimeException e) { - this.lastParsingFailure = e; - this.state = State.FAILED; - } - } else { - try { - this.parsedMessages.offer( - InvocationFlow.InvocationInput.of( - this.lastParsedMessageHeader, - this.lastParsedMessageHeader - .getType() - .messageParser() - .parseFrom( - this.internalBuffer - .readBytes(this.lastParsedMessageHeader.getLength()) - .nioBuffer()))); - this.state = State.WAITING_HEADER; - } catch (InvalidProtocolBufferException e) { - this.lastParsingFailure = new RuntimeException("Cannot parse the protobuf message", e); - this.state = State.FAILED; - } catch (RuntimeException e) { - this.lastParsingFailure = e; - this.state = State.FAILED; - } - } - } - } - - private int wantBytes() { - if (state == State.WAITING_HEADER) { - return 8; - } else { - return lastParsedMessageHeader.getLength(); - } - } -} diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/MessageEncoder.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/MessageEncoder.java deleted file mode 100644 index b14b7eea..00000000 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/MessageEncoder.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx; - -import com.google.protobuf.MessageLite; -import dev.restate.sdk.core.MessageHeader; -import io.vertx.core.buffer.Buffer; - -class MessageEncoder { - - static int encodeLength(MessageLite msg) { - return 8 + msg.getSerializedSize(); - } - - static Buffer encode(Buffer buffer, MessageLite msg) { - MessageHeader header = MessageHeader.fromMessage(msg); - - buffer.appendLong(header.encode()); - buffer.appendBytes(msg.toByteArray()); - - return buffer; - } -} diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java deleted file mode 100644 index a9bd7b0e..00000000 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx; - -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; -import static io.netty.handler.codec.http.HttpResponseStatus.*; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.core.ProtocolException; -import dev.restate.sdk.core.ResolvedEndpointHandler; -import dev.restate.sdk.core.RestateEndpoint; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import io.netty.util.AsciiString; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.context.propagation.TextMapGetter; -import io.reactiverse.contextual.logging.ContextualData; -import io.vertx.core.Context; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpServerRequest; -import io.vertx.core.http.HttpServerResponse; -import io.vertx.core.http.impl.HttpServerRequestInternal; -import java.net.URI; -import java.util.HashMap; -import java.util.concurrent.Executor; -import java.util.regex.Pattern; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jspecify.annotations.Nullable; - -class RequestHttpServerHandler implements Handler { - - private static final Logger LOG = LogManager.getLogger(RequestHttpServerHandler.class); - - private static final AsciiString APPLICATION_RESTATE = AsciiString.cached("application/restate"); - private static final ObjectMapper MANIFEST_OBJECT_MAPPER = new ObjectMapper(); - - private static final Pattern SLASH = Pattern.compile(Pattern.quote("/")); - - private static final String DISCOVER_PATH = "/discover"; - - static TextMapGetter OTEL_TEXT_MAP_GETTER = - new TextMapGetter<>() { - @Override - public Iterable keys(MultiMap carrier) { - return carrier.names(); - } - - @Nullable - @Override - public String get(@Nullable MultiMap carrier, String key) { - if (carrier == null) { - return null; - } - return carrier.get(key); - } - }; - - private final RestateEndpoint restateEndpoint; - private final HashMap blockingComponents; - private final OpenTelemetry openTelemetry; - - RequestHttpServerHandler( - RestateEndpoint restateEndpoint, - HashMap blockingComponents, - OpenTelemetry openTelemetry) { - this.restateEndpoint = restateEndpoint; - this.blockingComponents = blockingComponents; - this.openTelemetry = openTelemetry; - } - - @Override - public void handle(HttpServerRequest request) { - URI uri = URI.create(request.uri()); - - // Let's first check if it's a discovery request - if (DISCOVER_PATH.equalsIgnoreCase(uri.getPath())) { - this.handleDiscoveryRequest(request); - return; - } - - // Parse request - String[] pathSegments = SLASH.split(uri.getPath()); - if (pathSegments.length < 3) { - LOG.warn( - "Path doesn't match the pattern /invoke/ComponentName/HandlerName nor /discover: '{}'", - request.path()); - request.response().setStatusCode(NOT_FOUND.code()).end(); - return; - } - String componentName = pathSegments[pathSegments.length - 2]; - String handlerName = pathSegments[pathSegments.length - 1]; - boolean isBlockingComponent = blockingComponents.containsKey(componentName); - - // Parse OTEL context and generate span - final io.opentelemetry.context.Context otelContext = - openTelemetry - .getPropagators() - .getTextMapPropagator() - .extract( - io.opentelemetry.context.Context.current(), - request.headers(), - OTEL_TEXT_MAP_GETTER); - - Context vertxCurrentContext = ((HttpServerRequestInternal) request).context(); - - ResolvedEndpointHandler handler; - try { - handler = - restateEndpoint.resolve( - componentName, - handlerName, - otelContext, - new RestateEndpoint.LoggingContextSetter() { - @Override - public void setServiceMethod(String serviceMethod) { - ContextualData.put( - RestateEndpoint.LoggingContextSetter.COMPONENT_HANDLER_KEY, serviceMethod); - } - - @Override - public void setInvocationId(String id) { - ContextualData.put(RestateEndpoint.LoggingContextSetter.INVOCATION_ID_KEY, id); - } - - @Override - public void setInvocationStatus(String invocationStatus) { - ContextualData.put( - RestateEndpoint.LoggingContextSetter.INVOCATION_STATUS_KEY, invocationStatus); - } - }, - currentContextExecutor(vertxCurrentContext), - isBlockingComponent ? blockingExecutor(componentName) : null); - } catch (ProtocolException e) { - LOG.warn("Error when resolving the handler", e); - request - .response() - .setStatusCode( - e.getFailureCode() == TerminalException.Code.NOT_FOUND.value() - ? NOT_FOUND.code() - : INTERNAL_SERVER_ERROR.code()) - .end(); - return; - } - - LOG.debug("Handling request to " + componentName + "/" + handlerName); - - // Prepare the header frame to send in the response. - // Vert.x will send them as soon as we send the first write - HttpServerResponse response = request.response(); - response.setStatusCode(OK.code()); - response.putHeader(CONTENT_TYPE, APPLICATION_RESTATE); - // This is No-op for HTTP2 - response.setChunked(true); - - HttpRequestFlowAdapter requestFlowAdapter = new HttpRequestFlowAdapter(request); - HttpResponseFlowAdapter responseFlowAdapter = new HttpResponseFlowAdapter(response); - - requestFlowAdapter.subscribe(handler.input()); - handler.output().subscribe(responseFlowAdapter); - - handler.start(); - } - - private Executor currentContextExecutor(Context currentContext) { - return runnable -> currentContext.runOnContext(v -> runnable.run()); - } - - private Executor blockingExecutor(String serviceName) { - return this.blockingComponents.get(serviceName); - } - - private void handleDiscoveryRequest(HttpServerRequest request) { - // Compute response and write it back - DeploymentManifestSchema response = this.restateEndpoint.handleDiscoveryRequest(); - Buffer responseBuffer; - try { - responseBuffer = Buffer.buffer(MANIFEST_OBJECT_MAPPER.writeValueAsBytes(response)); - } catch (JsonProcessingException e) { - LOG.warn("Error when writing out the manifest POJO", e); - request.response().setStatusCode(INTERNAL_SERVER_ERROR.code()).end(); - return; - } - - request - .response() - .setStatusCode(OK.code()) - .putHeader(CONTENT_TYPE, APPLICATION_JSON) - .end(responseBuffer); - } -} diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java deleted file mode 100644 index 119384d0..00000000 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx; - -import dev.restate.sdk.common.BindableComponent; -import dev.restate.sdk.common.ComponentAdapter; -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import dev.restate.sdk.common.syscalls.ExecutorType; -import dev.restate.sdk.core.RestateEndpoint; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import io.opentelemetry.api.OpenTelemetry; -import io.vertx.core.AsyncResult; -import io.vertx.core.Vertx; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; -import java.util.*; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Endpoint builder for a Restate HTTP Endpoint using Vert.x, to serve Restate components. - * - *

This endpoint supports the Restate HTTP/2 Streaming component Protocol. - * - *

Example usage: - * - *

- * public static void main(String[] args) {
- *   RestateHttpEndpointBuilder.builder()
- *           .with(new Counter())
- *           .buildAndListen();
- * }
- * 
- */ -public class RestateHttpEndpointBuilder { - - private static final Logger LOG = LogManager.getLogger(RestateHttpEndpointBuilder.class); - - private final Vertx vertx; - private final RestateEndpoint.Builder endpointBuilder = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.BIDI_STREAM); - private final Executor defaultExecutor = Executors.newCachedThreadPool(); - private final HashMap blockingComponents = new HashMap<>(); - private OpenTelemetry openTelemetry = OpenTelemetry.noop(); - private HttpServerOptions options = - new HttpServerOptions() - .setPort(Optional.ofNullable(System.getenv("PORT")).map(Integer::parseInt).orElse(9080)); - - private RestateHttpEndpointBuilder(Vertx vertx) { - this.vertx = vertx; - } - - /** Create a new builder. */ - public static RestateHttpEndpointBuilder builder() { - return new RestateHttpEndpointBuilder(Vertx.vertx()); - } - - /** Create a new builder. */ - public static RestateHttpEndpointBuilder builder(Vertx vertx) { - return new RestateHttpEndpointBuilder(vertx); - } - - /** Add custom {@link HttpServerOptions} to the server used by the endpoint. */ - public RestateHttpEndpointBuilder withOptions(HttpServerOptions options) { - this.options = Objects.requireNonNull(options); - return this; - } - - /** - * Add a Restate component to the endpoint. This will automatically discover the adapter based on - * the class name. You can provide the adapter manually using {@link #with(Object, - * ComponentAdapter)} - */ - public RestateHttpEndpointBuilder with(Object component) { - return this.with(component, defaultExecutor); - } - - /** - * Add a Restate component to the endpoint, specifying the {@code executor} where to run the - * component code. This will automatically discover the adapter based on the class name. You can - * provide the adapter manually using {@link #with(Object, ComponentAdapter, Executor)} - * - *

You can run on virtual threads by using the executor {@code - * Executors.newVirtualThreadPerTaskExecutor()}. - */ - public RestateHttpEndpointBuilder with(Object component, Executor executor) { - return this.with(component, RestateEndpoint.discoverAdapter(component), executor); - } - - /** Add a Restate component to the endpoint, specifying an adapter. */ - public RestateHttpEndpointBuilder with(T component, ComponentAdapter adapter) { - return this.with(component, adapter, defaultExecutor); - } - - /** - * Add a Restate component to the endpoint, specifying the {@code executor} where to run the - * component code. - * - *

You can run on virtual threads by using the executor {@code - * Executors.newVirtualThreadPerTaskExecutor()}. - */ - public RestateHttpEndpointBuilder with( - T component, ComponentAdapter adapter, Executor executor) { - return this.with(adapter.adapt(component), executor); - } - - /** Add a Restate bindable component to the endpoint. */ - public RestateHttpEndpointBuilder with(BindableComponent component) { - return this.with(component, defaultExecutor); - } - - /** - * Add a Restate bindable component to the endpoint, specifying the {@code executor} where to run - * the component code. - * - *

You can run on virtual threads by using the executor {@code - * Executors.newVirtualThreadPerTaskExecutor()}. - */ - public RestateHttpEndpointBuilder with(BindableComponent component, Executor executor) { - for (ComponentDefinition componentDefinition : component.definitions()) { - this.endpointBuilder.with(componentDefinition); - if (componentDefinition.getExecutorType() == ExecutorType.BLOCKING) { - this.blockingComponents.put(componentDefinition.getFullyQualifiedServiceName(), executor); - } - } - - return this; - } - - /** - * Add a {@link OpenTelemetry} implementation for tracing and metrics. - * - * @see OpenTelemetry - */ - public RestateHttpEndpointBuilder withOpenTelemetry(OpenTelemetry openTelemetry) { - this.openTelemetry = openTelemetry; - return this; - } - - /** Build and listen on the specified port. */ - public void buildAndListen(int port) { - build().listen(port).onComplete(RestateHttpEndpointBuilder::handleStart); - } - - /** - * Build and listen on the port specified by the environment variable {@code PORT}, or - * alternatively on the default {@code 9080} port. - */ - public void buildAndListen() { - build().listen().onComplete(RestateHttpEndpointBuilder::handleStart); - } - - /** Build the {@link HttpServer} serving the Restate component endpoint. */ - public HttpServer build() { - HttpServer server = vertx.createHttpServer(options); - - this.endpointBuilder.withTracer(this.openTelemetry.getTracer("restate-java-sdk-vertx")); - - server.requestHandler( - new RequestHttpServerHandler( - this.endpointBuilder.build(), blockingComponents, openTelemetry)); - - return server; - } - - private static void handleStart(AsyncResult ar) { - if (ar.succeeded()) { - LOG.info("Restate HTTP Endpoint server started on port " + ar.result().actualPort()); - } else { - LOG.error("Restate HTTP Endpoint server start failed", ar.cause()); - } - } -} diff --git a/sdk-http-vertx/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider b/sdk-http-vertx/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider deleted file mode 100644 index 8939c1e0..00000000 --- a/sdk-http-vertx/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider +++ /dev/null @@ -1 +0,0 @@ -io.reactiverse.contextual.logging.VertxContextDataProvider \ No newline at end of file diff --git a/sdk-http-vertx/src/test/java/dev/restate/sdk/http/vertx/testservices/BlockingGreeter.java b/sdk-http-vertx/src/test/java/dev/restate/sdk/http/vertx/testservices/BlockingGreeter.java deleted file mode 100644 index 758a2031..00000000 --- a/sdk-http-vertx/src/test/java/dev/restate/sdk/http/vertx/testservices/BlockingGreeter.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx.testservices; - -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import java.time.Duration; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@VirtualObject -public class BlockingGreeter { - - private static final Logger LOG = LogManager.getLogger(BlockingGreeter.class); - public static final StateKey COUNTER = StateKey.of("counter", CoreSerdes.JSON_LONG); - - @Handler - public String greet(ObjectContext context, String request) { - LOG.info("Greet invoked!"); - - var count = context.get(COUNTER).orElse(0L) + 1; - context.set(COUNTER, count); - - context.sleep(Duration.ofSeconds(1)); - - return "Hello " + request + ". Count: " + count; - } -} diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt deleted file mode 100644 index 2361da49..00000000 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx - -import com.google.protobuf.MessageLite -import dev.restate.sdk.core.TestDefinitions.TestDefinition -import dev.restate.sdk.core.TestDefinitions.TestExecutor -import io.vertx.core.Vertx -import io.vertx.core.buffer.Buffer -import io.vertx.core.http.HttpHeaders -import io.vertx.core.http.HttpMethod -import io.vertx.core.http.HttpServerOptions -import io.vertx.kotlin.coroutines.coAwait -import io.vertx.kotlin.coroutines.dispatcher -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield -import org.assertj.core.api.Assertions - -class HttpVertxTestExecutor(private val vertx: Vertx) : TestExecutor { - override fun buffered(): Boolean { - return false - } - - override fun executeTest(definition: TestDefinition) { - runBlocking(vertx.dispatcher()) { - // This test infra supports only components returning one component definition - val componentDefinition = definition.component.definitions() - Assertions.assertThat(componentDefinition).size().isEqualTo(1) - - // Build server - val server = - RestateHttpEndpointBuilder.builder(vertx) - .withOptions(HttpServerOptions().setPort(0)) - .with(definition.component) - .build() - server.listen().coAwait() - - val client = vertx.createHttpClient(RestateHttpEndpointTest.HTTP_CLIENT_OPTIONS) - - val request = - client - .request( - HttpMethod.POST, - server.actualPort(), - "localhost", - "/invoke/${componentDefinition.get(0).fullyQualifiedServiceName}/${definition.method}") - .coAwait() - - // Prepare request header and send them - request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") - request.sendHead().coAwait() - - launch { - for (msg in definition.input) { - val buffer = Buffer.buffer(MessageEncoder.encodeLength(msg.message())) - buffer.appendLong(msg.header().encode()) - buffer.appendBytes(msg.message().toByteArray()) - request.write(buffer).coAwait() - yield() - } - - request.end().coAwait() - } - - val response = request.response().coAwait() - - // Start the coroutine to send input messages - - // Start the response receiver - val inputChannel = Channel() - val decoder = MessageDecoder() - response.handler { - decoder.offer(it) - while (true) { - val m = decoder.poll() ?: break - launch(vertx.dispatcher()) { inputChannel.send(m.message()) } - } - } - response.endHandler { inputChannel.close() } - response.resume() - - // Collect all the output messages - val messages = inputChannel.receiveAsFlow().toList() - definition.outputAssert.accept(messages) - - // Close the server - server.close().coAwait() - } - } -} diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTests.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTests.kt deleted file mode 100644 index ebdf2933..00000000 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTests.kt +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx - -import dev.restate.sdk.JavaBlockingTests -import dev.restate.sdk.JavaCodegenTests -import dev.restate.sdk.core.TestDefinitions.TestExecutor -import dev.restate.sdk.core.TestDefinitions.TestSuite -import dev.restate.sdk.kotlin.KotlinCoroutinesTests -import dev.restate.sdk.kotlin.KtCodegenTests -import io.vertx.core.Vertx -import java.util.stream.Stream -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll - -class HttpVertxTests : dev.restate.sdk.core.TestRunner() { - - lateinit var vertx: Vertx - - @BeforeAll - fun beforeAll() { - vertx = Vertx.vertx() - } - - @AfterAll - fun afterAll() { - vertx.close().toCompletionStage().toCompletableFuture().get() - } - - override fun executors(): Stream { - return Stream.of(HttpVertxTestExecutor(vertx)) - } - - override fun definitions(): Stream { - return Stream.concat( - Stream.concat( - Stream.concat(JavaBlockingTests().definitions(), JavaCodegenTests().definitions()), - Stream.concat(KotlinCoroutinesTests().definitions(), KtCodegenTests().definitions())), - Stream.of(VertxExecutorsTest())) - } -} diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt deleted file mode 100644 index 054b2224..00000000 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx - -import com.fasterxml.jackson.databind.ObjectMapper -import com.google.protobuf.ByteString -import com.google.protobuf.Empty -import com.google.protobuf.MessageLite -import dev.restate.generated.service.protocol.Protocol.* -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.core.ProtoUtils.* -import dev.restate.sdk.core.manifest.DeploymentManifestSchema -import dev.restate.sdk.http.vertx.testservices.BlockingGreeter -import dev.restate.sdk.http.vertx.testservices.greeter -import io.netty.handler.codec.http.HttpResponseStatus -import io.vertx.core.Vertx -import io.vertx.core.buffer.Buffer -import io.vertx.core.http.* -import io.vertx.junit5.Timeout -import io.vertx.junit5.VertxExtension -import io.vertx.kotlin.coroutines.coAwait -import io.vertx.kotlin.coroutines.dispatcher -import io.vertx.kotlin.coroutines.receiveChannelHandler -import java.util.concurrent.TimeUnit -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.parallel.Isolated - -@Isolated -@ExtendWith(VertxExtension::class) -internal class RestateHttpEndpointTest { - - companion object { - val HTTP_CLIENT_OPTIONS: HttpClientOptions = - HttpClientOptions() - // Set prior knowledge - .setProtocolVersion(HttpVersion.HTTP_2) - .setHttp2ClearTextUpgrade(false) - } - - @Timeout(value = 1, timeUnit = TimeUnit.SECONDS) - @Test - fun endpointWithNonBlockingService(vertx: Vertx): Unit = - greetTest(vertx, "KtGreeter") { it.with(greeter()) } - - @Timeout(value = 1, timeUnit = TimeUnit.SECONDS) - @Test - fun endpointWithBlockingService(vertx: Vertx): Unit = - greetTest(vertx, BlockingGreeter::class.simpleName!!) { it.with(BlockingGreeter()) } - - private fun greetTest( - vertx: Vertx, - componentName: String, - consumeBuilderFn: (RestateHttpEndpointBuilder) -> RestateHttpEndpointBuilder - ): Unit = - runBlocking(vertx.dispatcher()) { - val endpointBuilder = RestateHttpEndpointBuilder.builder(vertx) - consumeBuilderFn(endpointBuilder) - - val endpointPort: Int = - endpointBuilder - .withOptions(HttpServerOptions().setPort(0)) - .build() - .listen() - .coAwait() - .actualPort() - - val client = vertx.createHttpClient(HTTP_CLIENT_OPTIONS) - - val request = - client - .request(HttpMethod.POST, endpointPort, "localhost", "/invoke/$componentName/greet") - .coAwait() - - // Prepare request header - request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") - - // Send start message and PollInputStreamEntry - request.write(encode(startMessage(1).build())) - request.write(encode(inputMessage("Francesco"))) - - val response = request.response().coAwait() - - // Start the input decoder - val inputChannel = vertx.receiveChannelHandler() - val decoder = MessageDecoder() - response.handler { - decoder.offer(it) - while (true) { - val m = decoder.poll() ?: break - inputChannel.handle(m.message()) - } - } - response.resume() - - // Wait for Get State Entry - val getStateEntry = inputChannel.receive() - - assertThat(getStateEntry).isInstanceOf(GetStateEntryMessage::class.java) - assertThat(getStateEntry as GetStateEntryMessage) - .returns(ByteString.copyFromUtf8("counter"), GetStateEntryMessage::getKey) - - // Send completion - request.write( - encode( - completionMessage(1) - .setValue(CoreSerdes.JSON_LONG.serializeToByteString(2)) - .build())) - - // Wait for Set State Entry - val setStateEntry = inputChannel.receive() - - assertThat(setStateEntry).isInstanceOf(SetStateEntryMessage::class.java) - assertThat(setStateEntry as SetStateEntryMessage) - .returns(ByteString.copyFromUtf8("counter"), SetStateEntryMessage::getKey) - .returns(ByteString.copyFromUtf8("3"), SetStateEntryMessage::getValue) - - // Wait for the sleep and complete it - val sleepEntry = inputChannel.receive() - - assertThat(sleepEntry).isInstanceOf(SleepEntryMessage::class.java) - - // Wait a bit, then send the completion - delay(1.seconds) - request.write( - encode( - CompletionMessage.newBuilder() - .setEntryIndex(3) - .setEmpty(Empty.getDefaultInstance()) - .build())) - - // Now wait for response - val outputEntry = inputChannel.receive() - - assertThat(outputEntry).isInstanceOf(OutputStreamEntryMessage::class.java) - assertThat(outputEntry).isEqualTo(outputMessage("Hello Francesco. Count: 3")) - - // Wait for closing request and response - request.end().coAwait() - } - - @Test - fun return404(vertx: Vertx): Unit = - runBlocking(vertx.dispatcher()) { - val endpointPort: Int = - RestateHttpEndpointBuilder.builder(vertx) - .with(BlockingGreeter()) - .withOptions(HttpServerOptions().setPort(0)) - .build() - .listen() - .coAwait() - .actualPort() - - val client = vertx.createHttpClient(HTTP_CLIENT_OPTIONS) - - val request = - client - .request( - HttpMethod.POST, - endpointPort, - "localhost", - "/invoke/" + BlockingGreeter::class.java.simpleName + "/unknownMethod") - .coAwait() - - // Prepare request header - request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") - request.write(encode(startMessage(0).build())) - - val response = request.response().coAwait() - - // Response status should be 404 - assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.NOT_FOUND.code()) - - response.end().coAwait() - } - - @Test - fun serviceDiscovery(vertx: Vertx): Unit = - runBlocking(vertx.dispatcher()) { - val endpointPort: Int = - RestateHttpEndpointBuilder.builder(vertx) - .with(BlockingGreeter()) - .withOptions(HttpServerOptions().setPort(0)) - .build() - .listen() - .coAwait() - .actualPort() - - val client = vertx.createHttpClient(HTTP_CLIENT_OPTIONS) - - // Send request - val request = - client.request(HttpMethod.GET, endpointPort, "localhost", "/discover").coAwait() - request.end().coAwait() - - // Assert response - val response = request.response().coAwait() - - // Response status and content type header - assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code()) - assertThat(response.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/json") - - // Parse response - val responseBody = response.body().coAwait() - // Compute response and write it back - val discoveryResponse: DeploymentManifestSchema = - ObjectMapper().readValue(responseBody.bytes, DeploymentManifestSchema::class.java) - - assertThat(discoveryResponse.components) - .map { it.fullyQualifiedComponentName } - .containsOnly(BlockingGreeter::class.java.simpleName) - } - - fun encode(msg: MessageLite): Buffer { - val buffer = Buffer.buffer(MessageEncoder.encodeLength(msg)) - val header = headerFromMessage(msg) - buffer.appendLong(header.encode()) - buffer.appendBytes(msg.toByteArray()) - return buffer - } -} diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/VertxExecutorsTest.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/VertxExecutorsTest.kt deleted file mode 100644 index e5946d2c..00000000 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/VertxExecutorsTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx - -import com.google.protobuf.ByteString -import dev.restate.generated.sdk.java.Java -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.core.ProtoUtils.* -import dev.restate.sdk.core.TestDefinitions -import dev.restate.sdk.core.TestDefinitions.testInvocation -import io.vertx.core.Vertx -import java.util.stream.Stream -import kotlin.coroutines.coroutineContext -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.Dispatchers -import org.apache.logging.log4j.LogManager - -class VertxExecutorsTest : TestDefinitions.TestSuite { - - private val nonBlockingCoroutineName = CoroutineName("CheckContextSwitchingTestCoroutine") - - companion object { - private val LOG = LogManager.getLogger() - } - - private suspend fun checkNonBlockingComponentTrampolineExecutor( - ctx: dev.restate.sdk.kotlin.Context - ) { - LOG.info("I am on the thread I am before executing side effect") - check(Vertx.currentContext() == null) - check(coroutineContext[CoroutineName] == nonBlockingCoroutineName) - ctx.sideEffect { - LOG.info("I am on the thread I am when executing side effect") - check(coroutineContext[CoroutineName] == nonBlockingCoroutineName) - check(Vertx.currentContext() == null) - } - LOG.info("I am on the thread I am after executing side effect") - check(coroutineContext[CoroutineName] == nonBlockingCoroutineName) - check(Vertx.currentContext() == null) - } - - private fun checkBlockingComponentTrampolineExecutor( - ctx: dev.restate.sdk.Context, - _unused: Any - ): Void? { - val id = Thread.currentThread().id - check(Vertx.currentContext() == null) - ctx.sideEffect { - check(Thread.currentThread().id == id) - check(Vertx.currentContext() == null) - } - check(Thread.currentThread().id == id) - check(Vertx.currentContext() == null) - return null - } - - override fun definitions(): Stream { - return Stream.of( - testInvocation( - dev.restate.sdk.kotlin.Component.service( - "CheckBlockingComponentTrampolineExecutor", - Dispatchers.Default + nonBlockingCoroutineName) { - handler("do") { ctx, _: Unit -> - checkNonBlockingComponentTrampolineExecutor(ctx) - } - }, - "do") - .withInput(startMessage(1), inputMessage(), ackMessage(1)) - .onlyUnbuffered() - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder().setValue(ByteString.EMPTY), - outputMessage(), - END_MESSAGE), - testInvocation( - dev.restate.sdk.Component.service("CheckBlockingComponentTrampolineExecutor") - .with( - dev.restate.sdk.Component.HandlerSignature.of( - "do", CoreSerdes.VOID, CoreSerdes.VOID), - this::checkBlockingComponentTrampolineExecutor) - .build(), - "do") - .withInput(startMessage(1), inputMessage(), ackMessage(1)) - .onlyUnbuffered() - .expectingOutput( - Java.SideEffectEntryMessage.newBuilder().setValue(ByteString.EMPTY), - outputMessage(), - END_MESSAGE)) - } -} diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtComponent.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtComponent.kt deleted file mode 100644 index d2a49f45..00000000 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtComponent.kt +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.http.vertx.testservices - -import dev.restate.sdk.common.BindableComponent -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.kotlin.Component -import kotlin.time.Duration.Companion.seconds -import org.apache.logging.log4j.LogManager - -private val LOG = LogManager.getLogger() -private val COUNTER: StateKey = BlockingGreeter.COUNTER - -fun greeter(): BindableComponent = - Component.virtualObject("KtGreeter") { - handler("greet") { ctx, request: String -> - LOG.info("Greet invoked!") - - val count = (ctx.get(COUNTER) ?: 0) + 1 - ctx.set(COUNTER, count) - - ctx.sleep(1.seconds) - - "Hello $request. Count: $count" - } - } diff --git a/sdk-http-vertx/src/test/resources/junit-platform.properties b/sdk-http-vertx/src/test/resources/junit-platform.properties deleted file mode 100644 index 3e799af0..00000000 --- a/sdk-http-vertx/src/test/resources/junit-platform.properties +++ /dev/null @@ -1,3 +0,0 @@ -junit.jupiter.execution.parallel.enabled = true -junit.jupiter.execution.parallel.config.strategy = dynamic -junit.jupiter.execution.parallel.mode.default = same_thread \ No newline at end of file diff --git a/sdk-lambda/build.gradle.kts b/sdk-lambda/build.gradle.kts deleted file mode 100644 index fb05ac4e..00000000 --- a/sdk-lambda/build.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - `java-library` - kotlin("jvm") - `library-publishing-conventions` -} - -description = "Restate SDK AWS Lambda integration" - -dependencies { - api(project(":sdk-common")) - implementation(project(":sdk-core")) - - api(lambdaLibs.core) - api(lambdaLibs.events) - - // Jackson (we need it for the manifest) - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.databind) - - implementation(platform(coreLibs.opentelemetry.bom)) - implementation(coreLibs.opentelemetry.api) - - implementation(coreLibs.log4j.api) - - testAnnotationProcessor(project(":sdk-api-gen")) - testImplementation(project(":sdk-api")) - testImplementation(project(":sdk-api-kotlin")) - testImplementation(project(":sdk-core", "testArchive")) - testImplementation(project(":sdk-serde-jackson")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - - testImplementation(coreLibs.protobuf.java) - testImplementation(coreLibs.protobuf.kotlin) - testImplementation(coreLibs.log4j.core) - - testImplementation(kotlinLibs.kotlinx.coroutines) -} diff --git a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/BaseRestateLambdaHandler.java b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/BaseRestateLambdaHandler.java deleted file mode 100644 index d8e33193..00000000 --- a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/BaseRestateLambdaHandler.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.lambda; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import org.apache.logging.log4j.CloseableThreadContext; - -/** - * Base implementation of a Lambda handler to execute restate services - * - *

Implementation of AWS Lambda {@link - * RequestHandler} for serving Restate functions. - * - *

Restate can invoke Lambda functions directly or through AWS API gateway. For both cases, it - * will invoke the Lambda using the same envelope of an API Gateway request/response. See Restate Lambda documentation for - * more details. - */ -public abstract class BaseRestateLambdaHandler - implements RequestHandler { - - private static final String AWS_REQUEST_ID = "AWSRequestId"; - - private final RestateLambdaEndpoint restateLambdaEndpoint; - - protected BaseRestateLambdaHandler() { - RestateLambdaEndpointBuilder builder = RestateLambdaEndpoint.builder(); - register(builder); - this.restateLambdaEndpoint = builder.build(); - } - - /** Configure your services in this method. */ - public abstract void register(RestateLambdaEndpointBuilder builder); - - @Override - public APIGatewayProxyResponseEvent handleRequest( - APIGatewayProxyRequestEvent input, Context context) { - try (var requestId = CloseableThreadContext.put(AWS_REQUEST_ID, context.getAwsRequestId())) { - return restateLambdaEndpoint.handleRequest(input, context); - } - } -} diff --git a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/LambdaFlowAdapters.java b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/LambdaFlowAdapters.java deleted file mode 100644 index 6f1ca9c7..00000000 --- a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/LambdaFlowAdapters.java +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.lambda; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.MessageLite; -import dev.restate.sdk.core.InvocationFlow; -import dev.restate.sdk.core.MessageHeader; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Flow; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -class LambdaFlowAdapters { - - static class ResultSubscriber implements InvocationFlow.InvocationOutputSubscriber { - - private static final ByteBuffer LONG_CONVERSION_BUFFER = ByteBuffer.allocate(Long.BYTES); - - private final ByteArrayOutputStream outputStream; - private final CompletableFuture completionFuture; - - ResultSubscriber() { - this.completionFuture = new CompletableFuture<>(); - this.outputStream = new ByteArrayOutputStream(); - } - - @Override - public void onSubscribe(Flow.Subscription subscription) { - subscription.request(Long.MAX_VALUE); - } - - @Override - public void onNext(MessageLite item) { - LONG_CONVERSION_BUFFER.putLong(0, MessageHeader.fromMessage(item).encode()); - try { - outputStream.write(LONG_CONVERSION_BUFFER.array()); - item.writeTo(outputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public void onError(Throwable throwable) { - this.completionFuture.completeExceptionally(throwable); - } - - @Override - public void onComplete() { - this.completionFuture.complete(null); - } - - public byte[] getResult() throws Throwable { - try { - this.completionFuture.get(); - return outputStream.toByteArray(); - } catch (ExecutionException e) { - throw e.getCause(); - } - } - } - - static class BufferedPublisher implements InvocationFlow.InvocationInputPublisher { - - private static final Logger LOG = LogManager.getLogger(BufferedPublisher.class); - - private final ByteBuffer buffer; - - BufferedPublisher(ByteBuffer buffer) { - this.buffer = buffer.asReadOnlyBuffer(); - } - - private Flow.Subscriber inputMessagesSubscriber; - private long subscriberRequest = 0; - - @Override - public void subscribe(Flow.Subscriber subscriber) { - if (this.inputMessagesSubscriber != null) { - throw new IllegalStateException( - "Cannot register more than one subscriber to this publisher"); - } - // Make sure the new subscriber starts from beginning - this.buffer.rewind(); - - // Register the subscriber - this.inputMessagesSubscriber = subscriber; - this.inputMessagesSubscriber.onSubscribe( - new Flow.Subscription() { - @Override - public void request(long l) { - handleSubscriptionRequest(l); - } - - @Override - public void cancel() { - cancelSubscription(); - } - }); - } - - private void handleSubscriptionRequest(long l) { - // Update the subscriber request - if (l == Long.MAX_VALUE) { - this.subscriberRequest = l; - } else { - this.subscriberRequest += l; - // Overflow check - if (this.subscriberRequest < 0) { - this.subscriberRequest = Long.MAX_VALUE; - } - } - - // Now process the buffer - while (this.subscriberRequest > 0 && this.inputMessagesSubscriber != null) { - if (!buffer.hasRemaining()) { - this.handleBufferEnd(); - return; - } - - MessageHeader header; - MessageLite entry; - try { - header = MessageHeader.parse(buffer.getLong()); - - // Prepare the ByteBuffer and pass it to the Protobuf message parser - ByteBuffer messageBuffer = buffer.slice(); - messageBuffer.limit(header.getLength()); - entry = header.getType().messageParser().parseFrom(messageBuffer); - - // Move the buffer after this message - buffer.position(buffer.position() + header.getLength()); - } catch (InvalidProtocolBufferException | RuntimeException e) { - handleDecodingError(e); - return; - } - - LOG.trace("Received entry " + entry); - this.subscriberRequest--; - inputMessagesSubscriber.onNext(InvocationFlow.InvocationInput.of(header, entry)); - } - } - - private void handleDecodingError(Throwable e) { - this.inputMessagesSubscriber.onError(e); - this.cancelSubscription(); - } - - private void handleBufferEnd() { - LOG.trace("Request end"); - this.inputMessagesSubscriber.onComplete(); - this.cancelSubscription(); - } - - private void cancelSubscription() { - this.inputMessagesSubscriber = null; - } - } -} diff --git a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java deleted file mode 100644 index de7eff63..00000000 --- a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.lambda; - -import static dev.restate.sdk.lambda.LambdaFlowAdapters.*; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.core.ProtocolException; -import dev.restate.sdk.core.ResolvedEndpointHandler; -import dev.restate.sdk.core.RestateEndpoint; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.context.propagation.TextMapGetter; -import java.nio.ByteBuffer; -import java.util.*; -import java.util.regex.Pattern; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; - -/** Restate Lambda Endpoint. */ -public final class RestateLambdaEndpoint { - - private static final ObjectMapper MANIFEST_OBJECT_MAPPER = new ObjectMapper(); - - private static final Logger LOG = LogManager.getLogger(RestateLambdaEndpoint.class); - - private static final Pattern SLASH = Pattern.compile(Pattern.quote("/")); - private static final String INVOKE_PATH_SEGMENT = "invoke"; - private static final String DISCOVER_PATH = "/discover"; - private static final Map INVOKE_RESPONSE_HEADERS = - Map.of("content-type", "application/restate"); - private static final Map DISCOVER_RESPONSE_HEADERS = - Map.of("content-type", "application/json"); - - private static TextMapGetter> OTEL_HEADERS_GETTER = - new TextMapGetter<>() { - @Override - public Iterable keys(Map carrier) { - return carrier.keySet(); - } - - @Override - public String get(Map carrier, String key) { - if (carrier == null) { - return null; - } - return carrier.get(key); - } - }; - - private final RestateEndpoint restateEndpoint; - private final OpenTelemetry openTelemetry; - - RestateLambdaEndpoint(RestateEndpoint restateEndpoint, OpenTelemetry openTelemetry) { - this.restateEndpoint = restateEndpoint; - this.openTelemetry = openTelemetry; - } - - /** Create a new builder. */ - public static RestateLambdaEndpointBuilder builder() { - return new RestateLambdaEndpointBuilder(); - } - - /** Handle a Lambda request as Restate Lambda endpoint. */ - public APIGatewayProxyResponseEvent handleRequest( - APIGatewayProxyRequestEvent input, Context context) { - // Remove trailing path separator - String path = - input.getPath().endsWith("/") - ? input.getPath().substring(0, input.getPath().length() - 1) - : input.getPath(); - - if (path.endsWith(DISCOVER_PATH)) { - return this.handleDiscovery(); - } - - return this.handleInvoke(input); - } - - // --- Invoke request - - private APIGatewayProxyResponseEvent handleInvoke(APIGatewayProxyRequestEvent input) { - // Parse request - String[] pathSegments = SLASH.split(input.getPath()); - if (pathSegments.length < 3 - || !INVOKE_PATH_SEGMENT.equalsIgnoreCase(pathSegments[pathSegments.length - 3])) { - LOG.warn("Path doesn't match the pattern /invoke/SvcName/MethodName: '{}'", input.getPath()); - return new APIGatewayProxyResponseEvent().withStatusCode(404); - } - String componentName = pathSegments[pathSegments.length - 2]; - String handlerName = pathSegments[pathSegments.length - 1]; - - // Parse OTEL context and generate span - final io.opentelemetry.context.Context otelContext = - openTelemetry - .getPropagators() - .getTextMapPropagator() - .extract( - io.opentelemetry.context.Context.current(), - input.getHeaders(), - OTEL_HEADERS_GETTER); - - // Parse request body - final ByteBuffer requestBody = parseInputBody(input); - - // Resolve handler - ResolvedEndpointHandler handler; - try { - handler = - this.restateEndpoint.resolve( - componentName, - handlerName, - otelContext, - RestateEndpoint.LoggingContextSetter.THREAD_LOCAL_INSTANCE, - null, - null); - } catch (ProtocolException e) { - LOG.warn("Error when resolving the grpc handler", e); - return new APIGatewayProxyResponseEvent() - .withStatusCode( - e.getFailureCode() == TerminalException.Code.NOT_FOUND.value() ? 404 : 500); - } - - BufferedPublisher publisher = new BufferedPublisher(requestBody); - ResultSubscriber subscriber = new ResultSubscriber(); - - // Wire handler - publisher.subscribe(handler.input()); - handler.output().subscribe(subscriber); - - // Start - handler.start(); - - // Await the result - byte[] responseBody; - try { - responseBody = subscriber.getResult(); - } catch (Error | RuntimeException e) { - throw e; - } catch (Throwable e) { - throw new RuntimeException(e); - } - - // Clear logging - ThreadContext.clearAll(); - - final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); - response.setHeaders(INVOKE_RESPONSE_HEADERS); - response.setIsBase64Encoded(true); - response.setStatusCode(200); - response.setBody(Base64.getEncoder().encodeToString(responseBody)); - return response; - } - - // --- Service discovery - - private APIGatewayProxyResponseEvent handleDiscovery() { - // Compute response and write it back - DeploymentManifestSchema responseManifest = this.restateEndpoint.handleDiscoveryRequest(); - byte[] serializedManifest; - try { - serializedManifest = MANIFEST_OBJECT_MAPPER.writeValueAsBytes(responseManifest); - } catch (JsonProcessingException e) { - LOG.warn("Error when writing out the manifest POJO", e); - final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); - response.setStatusCode(500); - response.setBody(e.getMessage()); - return response; - } - - final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); - response.setHeaders(DISCOVER_RESPONSE_HEADERS); - response.setIsBase64Encoded(true); - response.setStatusCode(200); - response.setBody(Base64.getEncoder().encodeToString(serializedManifest)); - return response; - } - - // --- Utils - - private static ByteBuffer parseInputBody(APIGatewayProxyRequestEvent input) { - if (input.getBody() == null) { - return ByteBuffer.wrap(new byte[] {}); - } - if (!input.getIsBase64Encoded()) { - throw new IllegalArgumentException( - "Input is not Base64 encoded. This is most likely an SDK bug, please contact the developers."); - } - return ByteBuffer.wrap(Base64.getDecoder().decode(input.getBody())); - } -} diff --git a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java deleted file mode 100644 index 0313d53a..00000000 --- a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.lambda; - -import dev.restate.sdk.common.BindableComponent; -import dev.restate.sdk.common.ComponentAdapter; -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import dev.restate.sdk.core.RestateEndpoint; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import io.opentelemetry.api.OpenTelemetry; - -/** Endpoint builder for a Restate AWS Lambda Endpoint, to serve Restate service. */ -public final class RestateLambdaEndpointBuilder { - - private final RestateEndpoint.Builder restateEndpoint = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.REQUEST_RESPONSE); - private OpenTelemetry openTelemetry = OpenTelemetry.noop(); - - /** - * Add a Restate entity to the endpoint, specifying the {@code executor} where to run the entity - * code. - */ - public RestateLambdaEndpointBuilder with(Object service) { - return this.with(service, RestateEndpoint.discoverAdapter(service)); - } - - public RestateLambdaEndpointBuilder with(T service, ComponentAdapter adapter) { - return this.with(adapter.adapt(service)); - } - - /** Add a Restate bindable component to the endpoint. */ - public RestateLambdaEndpointBuilder with(BindableComponent component) { - for (ComponentDefinition componentDefinition : component.definitions()) { - this.restateEndpoint.with(componentDefinition); - } - - return this; - } - - /** - * Add a {@link OpenTelemetry} implementation for tracing and metrics. - * - * @see OpenTelemetry - */ - public RestateLambdaEndpointBuilder withOpenTelemetry(OpenTelemetry openTelemetry) { - this.openTelemetry = openTelemetry; - return this; - } - - /** Build the {@link RestateLambdaEndpoint} serving the Restate service endpoint. */ - public RestateLambdaEndpoint build() { - return new RestateLambdaEndpoint(this.restateEndpoint.build(), this.openTelemetry); - } -} diff --git a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java deleted file mode 100644 index cbebfda8..00000000 --- a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.lambda; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.amazonaws.services.lambda.runtime.ClientContext; -import com.amazonaws.services.lambda.runtime.CognitoIdentity; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.protobuf.ByteString; -import com.google.protobuf.MessageLite; -import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.core.ProtoUtils; -import dev.restate.sdk.core.manifest.Component; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import dev.restate.sdk.lambda.testservices.JavaCounterClient; -import dev.restate.sdk.lambda.testservices.MyServicesHandler; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.*; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -class LambdaHandlerTest { - - @ValueSource(strings = {JavaCounterClient.COMPONENT_NAME, "KtCounter"}) - @ParameterizedTest - public void testInvoke(String serviceName) throws IOException { - MyServicesHandler handler = new MyServicesHandler(); - - // Mock request - APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - request.setHeaders(Map.of("content-type", "application/restate")); - request.setPath("/a/path/prefix/invoke/" + serviceName + "/get"); - request.setHttpMethod("POST"); - request.setIsBase64Encoded(true); - request.setBody( - Base64.getEncoder() - .encodeToString( - serializeEntries( - Protocol.StartMessage.newBuilder() - .setDebugId("123") - .setId(ByteString.copyFromUtf8("123")) - .setKnownEntries(1) - .setPartialState(true) - .build(), - Protocol.PollInputStreamEntryMessage.newBuilder() - .setValue(ByteString.EMPTY) - .build()))); - - // Send request - APIGatewayProxyResponseEvent response = handler.handleRequest(request, mockContext()); - - // Assert response - assertThat(response.getStatusCode()).isEqualTo(200); - assertThat(response.getHeaders()).containsEntry("content-type", "application/restate"); - assertThat(response.getIsBase64Encoded()).isTrue(); - assertThat(response.getBody()) - .asBase64Decoded() - .isEqualTo( - serializeEntries( - Protocol.GetStateEntryMessage.newBuilder() - .setKey(ByteString.copyFromUtf8("counter")) - .build(), - Protocol.SuspensionMessage.newBuilder().addEntryIndexes(1).build())); - } - - @Test - public void testDiscovery() throws IOException { - BaseRestateLambdaHandler handler = new MyServicesHandler(); - - // Mock request - APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - request.setPath("/a/path/prefix/discover"); - - // Send request - APIGatewayProxyResponseEvent response = handler.handleRequest(request, mockContext()); - - // Assert response - assertThat(response.getStatusCode()).isEqualTo(200); - assertThat(response.getHeaders()).containsEntry("content-type", "application/json"); - assertThat(response.getIsBase64Encoded()).isTrue(); - byte[] decodedStringResponse = Base64.getDecoder().decode(response.getBody()); - // Compute response and write it back - DeploymentManifestSchema discoveryResponse = - new ObjectMapper().readValue(decodedStringResponse, DeploymentManifestSchema.class); - - assertThat(discoveryResponse.getComponents()) - .map(Component::getFullyQualifiedComponentName) - .containsOnly(JavaCounterClient.COMPONENT_NAME, "KtCounter"); - } - - private static byte[] serializeEntries(MessageLite... msgs) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - for (MessageLite msg : msgs) { - ByteBuffer headerBuf = ByteBuffer.allocate(8); - headerBuf.putLong(ProtoUtils.headerFromMessage(msg).encode()); - outputStream.write(headerBuf.array()); - msg.writeTo(outputStream); - } - return outputStream.toByteArray(); - } - - private Context mockContext() { - return new Context() { - @Override - public String getAwsRequestId() { - return null; - } - - @Override - public String getLogGroupName() { - return null; - } - - @Override - public String getLogStreamName() { - return null; - } - - @Override - public String getFunctionName() { - return null; - } - - @Override - public String getFunctionVersion() { - return null; - } - - @Override - public String getInvokedFunctionArn() { - return null; - } - - @Override - public CognitoIdentity getIdentity() { - return null; - } - - @Override - public ClientContext getClientContext() { - return null; - } - - @Override - public int getRemainingTimeInMillis() { - return 0; - } - - @Override - public int getMemoryLimitInMB() { - return 0; - } - - @Override - public LambdaLogger getLogger() { - return null; - } - }; - } -} diff --git a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/JavaCounterService.java b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/JavaCounterService.java deleted file mode 100644 index eb8c973b..00000000 --- a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/JavaCounterService.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.lambda.testservices; - -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.StateKey; -import java.nio.charset.StandardCharsets; - -@VirtualObject(name = "JavaCounter") -public class JavaCounterService { - - public static final StateKey COUNTER = - StateKey.of( - "counter", - Serde.using( - l -> l.toString().getBytes(StandardCharsets.UTF_8), - v -> Long.parseLong(new String(v, StandardCharsets.UTF_8)))); - - @Handler - public Long get(ObjectContext context) { - return context.get(COUNTER).orElse(-1L); - } -} diff --git a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/MyServicesHandler.java b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/MyServicesHandler.java deleted file mode 100644 index ef498615..00000000 --- a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/MyServicesHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.lambda.testservices; - -import dev.restate.sdk.lambda.BaseRestateLambdaHandler; -import dev.restate.sdk.lambda.RestateLambdaEndpointBuilder; - -public class MyServicesHandler extends BaseRestateLambdaHandler { - @Override - public void register(RestateLambdaEndpointBuilder builder) { - builder.with(new JavaCounterService()).with(KotlinCounterServiceKt.counter()); - } -} diff --git a/sdk-lambda/src/test/kotlin/dev/restate/sdk/lambda/testservices/KotlinCounterService.kt b/sdk-lambda/src/test/kotlin/dev/restate/sdk/lambda/testservices/KotlinCounterService.kt deleted file mode 100644 index 1ab95e23..00000000 --- a/sdk-lambda/src/test/kotlin/dev/restate/sdk/lambda/testservices/KotlinCounterService.kt +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.lambda.testservices - -import dev.restate.sdk.common.BindableComponent -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.kotlin.Component -import java.nio.charset.StandardCharsets - -private val COUNTER: StateKey = - StateKey.of( - "counter", - Serde.using( - { l: Long -> l.toString().toByteArray(StandardCharsets.UTF_8) }, - { v: ByteArray? -> String(v!!, StandardCharsets.UTF_8).toLong() })) - -fun counter(): BindableComponent = - Component.virtualObject("KtCounter") { - handler("get") { ctx, _: Unit -> ctx.get(COUNTER) ?: -1 } - } diff --git a/sdk-lambda/src/test/resources/log4j2.properties b/sdk-lambda/src/test/resources/log4j2.properties deleted file mode 100644 index 5933d7fa..00000000 --- a/sdk-lambda/src/test/resources/log4j2.properties +++ /dev/null @@ -1,8 +0,0 @@ -rootLogger.level = DEBUG -rootLogger.appenderRef.testlogger.ref = TestLogger - -appender.testlogger.name = TestLogger -appender.testlogger.type = CONSOLE -appender.testlogger.target = SYSTEM_ERR -appender.testlogger.layout.type = PatternLayout -appender.testlogger.layout.pattern = %-4r [%t] %-5p %c %x - %m%n \ No newline at end of file diff --git a/sdk-serde-jackson/build.gradle.kts b/sdk-serde-jackson/build.gradle.kts deleted file mode 100644 index 2f3c38a7..00000000 --- a/sdk-serde-jackson/build.gradle.kts +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` -} - -description = "Restate SDK Jackson integration" - -dependencies { - compileOnly(coreLibs.jspecify) - - api(platform(jacksonLibs.jackson.bom)) - api(jacksonLibs.jackson.databind) - implementation(jacksonLibs.jackson.core) - - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - - implementation(project(":sdk-common")) -} diff --git a/sdk-serde-jackson/src/main/java/dev/restate/sdk/serde/jackson/JacksonSerdes.java b/sdk-serde-jackson/src/main/java/dev/restate/sdk/serde/jackson/JacksonSerdes.java deleted file mode 100644 index 208634a4..00000000 --- a/sdk-serde-jackson/src/main/java/dev/restate/sdk/serde/jackson/JacksonSerdes.java +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.serde.jackson; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.restate.sdk.common.Serde; -import java.io.IOException; -import org.jspecify.annotations.Nullable; - -/** - * {@link Serde} implementations for Jackson. - * - *

You can use these serdes for serializing and deserializing state, side effects results and - * awakeables using Jackson's {@link ObjectMapper}. - * - *

For example: - * - *

{@code
- * private static final StateKey PERSON = StateKey.of("person", JacksonSerdes.of(Person.class));
- * }
- * - * Or using Jackson's {@link TypeReference} to encapsulate generics: - * - *
{@code
- * private static final StateKey> PEOPLE = StateKey.of("people", JacksonSerdes.of(new TypeReference<>() {}));
- * }
- * - * When no object mapper is provided, a default one is used, using the default {@link - * com.fasterxml.jackson.core.JsonFactory} and discovering SPI modules. - */ -public final class JacksonSerdes { - - private JacksonSerdes() {} - - private static final ObjectMapper defaultMapper; - - static { - defaultMapper = new ObjectMapper(); - // Find modules through SPI (e.g. jackson-datatype-jsr310) - defaultMapper.findAndRegisterModules(); - } - - /** Serialize/Deserialize class using the default object mapper. */ - public static Serde of(Class clazz) { - return of(defaultMapper, clazz); - } - - /** Serialize/Deserialize class using the provided object mapper. */ - public static Serde of(ObjectMapper mapper, Class clazz) { - return new Serde<>() { - @Override - public byte[] serialize(@Nullable T value) { - try { - return mapper.writeValueAsBytes(value); - } catch (JsonProcessingException e) { - throw new RuntimeException("Cannot serialize value", e); - } - } - - @Override - public T deserialize(byte[] value) { - try { - return mapper.readValue(value, clazz); - } catch (IOException e) { - throw new RuntimeException("Cannot deserialize value", e); - } - } - - @Override - public String contentType() { - return "application/json"; - } - }; - } - - /** Serialize/Deserialize {@link TypeReference} using the default object mapper. */ - public static Serde of(TypeReference typeReference) { - return of(defaultMapper, typeReference); - } - - /** Serialize/Deserialize {@link TypeReference} using the default object mapper. */ - public static Serde of(ObjectMapper mapper, TypeReference typeReference) { - return new Serde<>() { - @Override - public byte[] serialize(@Nullable T value) { - try { - return mapper.writeValueAsBytes(value); - } catch (JsonProcessingException e) { - throw new RuntimeException("Cannot serialize value", e); - } - } - - @Override - public T deserialize(byte[] value) { - try { - return mapper.readValue(value, typeReference); - } catch (IOException e) { - throw new RuntimeException("Cannot deserialize value", e); - } - } - - @Override - public String contentType() { - return "application/json"; - } - }; - } -} diff --git a/sdk-serde-jackson/src/test/java/dev/restate/sdk/serde/jackson/JacksonSerdesTest.java b/sdk-serde-jackson/src/test/java/dev/restate/sdk/serde/jackson/JacksonSerdesTest.java deleted file mode 100644 index cc702f21..00000000 --- a/sdk-serde-jackson/src/test/java/dev/restate/sdk/serde/jackson/JacksonSerdesTest.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.serde.jackson; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.type.TypeReference; -import dev.restate.sdk.common.Serde; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class JacksonSerdesTest { - - public static class Person { - - private final String name; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public Person(@JsonProperty("name") String name) { - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return name; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Person person = (Person) o; - return Objects.equals(name, person.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } - } - - private static Stream roundtripTestCases() { - return Stream.of( - Arguments.of(new Person("Francesco"), JacksonSerdes.of(Person.class)), - Arguments.of( - List.of(new Person("Francesco"), new Person("Till")), - JacksonSerdes.of(new TypeReference>() {})), - Arguments.of( - Set.of(new Person("Francesco"), new Person("Till")), - JacksonSerdes.of(new TypeReference>() {}))); - } - - @ParameterizedTest(name = "{0}") - @MethodSource("roundtripTestCases") - void roundtrip(T value, Serde serde) { - assertThat(serde.deserialize(serde.serialize(value))).isEqualTo(value); - } -} diff --git a/sdk-testing/build.gradle.kts b/sdk-testing/build.gradle.kts deleted file mode 100644 index 8692bfd4..00000000 --- a/sdk-testing/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` -} - -description = "Restate SDK testing tools" - -dependencies { - api(project(":sdk-common")) - api(testingLibs.junit.api) - api(testingLibs.testcontainers.core) - - implementation(project(":admin-client")) - implementation(project(":sdk-http-vertx")) - implementation(coreLibs.log4j.api) - implementation(platform(vertxLibs.vertx.bom)) - implementation(vertxLibs.vertx.core) - - testImplementation(project(":sdk-api")) - testAnnotationProcessor(project(":sdk-api-gen")) - testImplementation(project(":sdk-serde-jackson")) - testImplementation(testingLibs.assertj) - testImplementation(testingLibs.junit.jupiter) - testImplementation(coreLibs.log4j.core) -} diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/BaseRestateRunner.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/BaseRestateRunner.java deleted file mode 100644 index c51d1dc3..00000000 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/BaseRestateRunner.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import dev.restate.admin.client.ApiClient; -import dev.restate.sdk.client.IngressClient; -import java.net.URL; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; - -abstract class BaseRestateRunner implements ParameterResolver { - - static final Namespace NAMESPACE = Namespace.create(BaseRestateRunner.class); - static final String DEPLOYER_KEY = "Deployer"; - - @Override - public boolean supportsParameter( - ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - return (parameterContext.isAnnotated(RestateAdminClient.class) - && ApiClient.class.isAssignableFrom(parameterContext.getParameter().getType())) - || (parameterContext.isAnnotated(RestateIngressClient.class) - && IngressClient.class.isAssignableFrom(parameterContext.getParameter().getType())) - || (parameterContext.isAnnotated(RestateURL.class) - && (String.class.isAssignableFrom(parameterContext.getParameter().getType()) - || URL.class.isAssignableFrom(parameterContext.getParameter().getType()))); - } - - @Override - public Object resolveParameter( - ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - if (parameterContext.isAnnotated(RestateAdminClient.class)) { - return getDeployer(extensionContext).getAdminClient(); - } else if (parameterContext.isAnnotated(RestateIngressClient.class)) { - return resolveIngressClient(extensionContext); - } else if (parameterContext.isAnnotated(RestateURL.class)) { - URL url = getDeployer(extensionContext).getIngressUrl(); - if (parameterContext.getParameter().getType().equals(String.class)) { - return url.toString(); - } - return url; - } - throw new ParameterResolutionException("The parameter is not supported"); - } - - private IngressClient resolveIngressClient(ExtensionContext extensionContext) { - URL url = getDeployer(extensionContext).getIngressUrl(); - return IngressClient.defaultClient(url.toString()); - } - - private ManualRestateRunner getDeployer(ExtensionContext extensionContext) { - return (ManualRestateRunner) extensionContext.getStore(NAMESPACE).get(DEPLOYER_KEY); - } -} diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/ManualRestateRunner.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/ManualRestateRunner.java deleted file mode 100644 index 72b4114c..00000000 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/ManualRestateRunner.java +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import dev.restate.admin.api.DeploymentApi; -import dev.restate.admin.client.ApiClient; -import dev.restate.admin.client.ApiException; -import dev.restate.admin.model.RegisterDeploymentRequest; -import dev.restate.admin.model.RegisterDeploymentRequestAnyOf; -import dev.restate.admin.model.RegisterDeploymentResponse; -import io.vertx.core.http.HttpServer; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.testcontainers.Testcontainers; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.containers.wait.strategy.WaitAllStrategy; -import org.testcontainers.images.builder.Transferable; -import org.testcontainers.utility.DockerImageName; - -/** Manual runner for Restate. We recommend using {@link RestateRunner} with JUnit 5. */ -public class ManualRestateRunner - implements AutoCloseable, ExtensionContext.Store.CloseableResource { - - private static final Logger LOG = LogManager.getLogger(ManualRestateRunner.class); - - private static final String RESTATE_RUNTIME = "runtime"; - public static final int RESTATE_INGRESS_ENDPOINT_PORT = 8080; - public static final int RESTATE_ADMIN_ENDPOINT_PORT = 9070; - - private final HttpServer server; - private final GenericContainer runtimeContainer; - - ManualRestateRunner( - HttpServer server, - String runtimeContainerImage, - Map additionalEnv, - String configFile) { - this.server = server; - this.runtimeContainer = new GenericContainer<>(DockerImageName.parse(runtimeContainerImage)); - - // Configure runtimeContainer - this.runtimeContainer - // We expose these ports only to enable port checks - .withExposedPorts(RESTATE_INGRESS_ENDPOINT_PORT, RESTATE_ADMIN_ENDPOINT_PORT) - .withEnv(additionalEnv) - // These envs should not be overriden by additionalEnv - .withEnv("RESTATE_META__REST_ADDRESS", "0.0.0.0:" + RESTATE_ADMIN_ENDPOINT_PORT) - .withEnv( - "RESTATE_WORKER__INGRESS__BIND_ADDRESS", "0.0.0.0:" + RESTATE_INGRESS_ENDPOINT_PORT) - .withNetworkAliases(RESTATE_RUNTIME) - // Configure wait strategy on health paths - .setWaitStrategy( - new WaitAllStrategy() - .withStrategy(Wait.forHttp("/health").forPort(RESTATE_ADMIN_ENDPOINT_PORT)) - .withStrategy( - Wait.forHttp("/restate/health").forPort(RESTATE_INGRESS_ENDPOINT_PORT))); - - if (configFile != null) { - this.runtimeContainer.withCopyToContainer(Transferable.of(configFile), "/config.yaml"); - this.runtimeContainer.withEnv("RESTATE_CONFIG", "/config.yaml"); - } - } - - /** Run restate, run the embedded service endpoint server, and register the services. */ - public void run() { - // Start listening the local server - try { - server.listen(0).toCompletionStage().toCompletableFuture().get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - - // Expose the server port - int serviceEndpointPort = server.actualPort(); - LOG.debug("Started embedded service endpoint server on port {}", serviceEndpointPort); - Testcontainers.exposeHostPorts(serviceEndpointPort); - - // Now create the runtime container and deploy it - this.runtimeContainer.start(); - LOG.debug("Started Restate container"); - - // Register services now - ApiClient client = getAdminClient(); - try { - RegisterDeploymentResponse response = - new DeploymentApi(client) - .createDeployment( - new RegisterDeploymentRequest( - new RegisterDeploymentRequestAnyOf() - .uri("http://host.testcontainers.internal:" + serviceEndpointPort))); - LOG.debug( - "Registered components {}", - response.getComponents().stream() - .map(dev.restate.admin.model.ComponentMetadata::getName) - .collect(Collectors.toList())); - } catch (ApiException e) { - throw new RuntimeException(e); - } - } - - /** - * Get restate ingress url to send HTTP/gRPC requests to services. - * - * @throws IllegalStateException if the restate container is not running. - */ - public URL getRestateUrl() { - try { - return new URL( - "http", - runtimeContainer.getHost(), - runtimeContainer.getMappedPort(RESTATE_INGRESS_ENDPOINT_PORT), - "/"); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - /** - * Get restate admin url to send HTTP requests to the admin API. - * - * @throws IllegalStateException if the restate container is not running. - */ - public URL getAdminUrl() { - try { - return new URL( - "http", - runtimeContainer.getHost(), - runtimeContainer.getMappedPort(RESTATE_ADMIN_ENDPOINT_PORT), - "/"); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - /** Get the restate container. */ - public GenericContainer getRestateContainer() { - return this.runtimeContainer; - } - - /** Stop restate and the embedded service endpoint server. */ - public void stop() { - this.close(); - } - - /** Like {@link #stop()}. */ - @Override - public void close() { - runtimeContainer.stop(); - LOG.debug("Stopped Restate container"); - server.close().toCompletionStage().toCompletableFuture().join(); - LOG.debug("Stopped Embedded Service endpoint server"); - } - - // -- Methods used by the JUnit5 extension - - ApiClient getAdminClient() { - return new ApiClient() - .setHost(runtimeContainer.getHost()) - .setPort(runtimeContainer.getMappedPort(RESTATE_ADMIN_ENDPOINT_PORT)); - } - - URL getIngressUrl() { - try { - return new URL( - "http", - runtimeContainer.getHost(), - runtimeContainer.getMappedPort(RESTATE_INGRESS_ENDPOINT_PORT), - "/"); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateAdminClient.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateAdminClient.java deleted file mode 100644 index 6bf7b404..00000000 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateAdminClient.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Inject the Restate {@link dev.restate.admin.client.ApiClient}, useful to build admin clients, - * such as {@link dev.restate.admin.api.DeploymentApi}. - */ -@Target(value = ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface RestateAdminClient {} diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateIngressClient.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateIngressClient.java deleted file mode 100644 index 3d16bbd6..00000000 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateIngressClient.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** Inject a {@link dev.restate.sdk.client.IngressClient} to interact with the deployed runtime. */ -@Target(value = ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface RestateIngressClient {} diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunner.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunner.java deleted file mode 100644 index 588191ac..00000000 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunner.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * Restate runner for JUnit 5. Example: - * - *
{@code
- * {@literal @}RegisterExtension
- * private final static RestateRunner restateRunner = RestateRunnerBuilder.create()
- *         .withService(new MyService())
- *         .buildRunner();
- * }
- * - *

The runner will deploy the services locally, execute Restate as container using - * testcontainers, and register the services. - * - *

This extension is scoped per test class, meaning that the restate runner will be shared among - * test methods. - * - *

Use the annotations {@link RestateIngressClient}, {@link RestateURL} and {@link - * RestateAdminClient} to interact with the deployed runtime: - * - *

{@code
- * {@literal @}Test
- * void testGreet({@literal @}RestateGrpcChannel ManagedChannel channel) {
- *     CounterGrpc.CounterBlockingStub client = CounterGrpc.newBlockingStub(channel);
- *     // Use client
- * }
- * }
- */ -public class RestateRunner extends BaseRestateRunner implements BeforeAllCallback { - private final ManualRestateRunner deployer; - - RestateRunner(ManualRestateRunner deployer) { - this.deployer = deployer; - } - - @Override - public void beforeAll(ExtensionContext context) { - deployer.run(); - context.getStore(NAMESPACE).put(DEPLOYER_KEY, deployer); - } -} diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunnerBuilder.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunnerBuilder.java deleted file mode 100644 index b07efcaa..00000000 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunnerBuilder.java +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import dev.restate.sdk.common.BindableComponent; -import dev.restate.sdk.common.ComponentAdapter; -import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executor; - -/** Builder for {@link RestateRunner}. See {@link RestateRunner} for more details. */ -public class RestateRunnerBuilder { - - private static final String DEFAULT_RESTATE_CONTAINER = "docker.io/restatedev/restate"; - private final RestateHttpEndpointBuilder endpointBuilder; - private String restateContainerImage = DEFAULT_RESTATE_CONTAINER; - private final Map additionalEnv = new HashMap<>(); - private String configFile; - - RestateRunnerBuilder(RestateHttpEndpointBuilder endpointBuilder) { - this.endpointBuilder = endpointBuilder; - } - - /** Override the container image to use for the Restate runtime. */ - public RestateRunnerBuilder withRestateContainerImage(String restateContainerImage) { - this.restateContainerImage = restateContainerImage; - return this; - } - - /** Add additional environment variables to the Restate container. */ - public RestateRunnerBuilder withAdditionalEnv(String key, String value) { - this.additionalEnv.put(key, value); - return this; - } - - /** Mount a config file in the Restate container. */ - public RestateRunnerBuilder withConfigFile(String configFile) { - this.configFile = configFile; - return this; - } - - /** - * Add a Restate component to the endpoint. This will automatically discover the adapter based on - * the class name. You can provide the adapter manually using {@link #with(Object, - * ComponentAdapter)} - */ - public RestateRunnerBuilder with(Object component) { - endpointBuilder.with(component); - return this; - } - - /** - * Add a Restate component to the endpoint, specifying the {@code executor} where to run the - * component code. This will automatically discover the adapter based on the class name. You can - * provide the adapter manually using {@link #with(Object, ComponentAdapter, Executor)} - * - *

You can run on virtual threads by using the executor {@code - * Executors.newVirtualThreadPerTaskExecutor()}. - */ - public RestateRunnerBuilder with(Object component, Executor executor) { - endpointBuilder.with(component, executor); - return this; - } - - /** Add a Restate component to the endpoint, specifying an adapter. */ - public RestateRunnerBuilder with(T component, ComponentAdapter adapter) { - endpointBuilder.with(component, adapter); - return this; - } - - /** - * Add a Restate component to the endpoint, specifying the {@code executor} where to run the - * component code. - * - *

You can run on virtual threads by using the executor {@code - * Executors.newVirtualThreadPerTaskExecutor()}. - */ - public RestateRunnerBuilder with( - T component, ComponentAdapter adapter, Executor executor) { - endpointBuilder.with(component, adapter, executor); - return this; - } - - /** Add a Restate bindable component to the endpoint. */ - public RestateRunnerBuilder with(BindableComponent component) { - endpointBuilder.with(component); - return this; - } - - /** - * Add a Restate bindable component to the endpoint, specifying the {@code executor} where to run - * the component code. - * - *

You can run on virtual threads by using the executor {@code - * Executors.newVirtualThreadPerTaskExecutor()}. - */ - public RestateRunnerBuilder with(BindableComponent component, Executor executor) { - endpointBuilder.with(component, executor); - return this; - } - - public ManualRestateRunner buildManualRunner() { - return new ManualRestateRunner( - this.endpointBuilder.build(), - this.restateContainerImage, - this.additionalEnv, - this.configFile); - } - - public RestateRunner buildRunner() { - return new RestateRunner(this.buildManualRunner()); - } - - public static RestateRunnerBuilder create() { - return new RestateRunnerBuilder(RestateHttpEndpointBuilder.builder()); - } - - /** Create from {@link RestateHttpEndpointBuilder}. */ - public static RestateRunnerBuilder of(RestateHttpEndpointBuilder endpointBuilder) { - return new RestateRunnerBuilder(endpointBuilder); - } -} diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateURL.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateURL.java deleted file mode 100644 index c7d93d5b..00000000 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateURL.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.net.URL; - -/** - * Inject Restate's URL (either {@link String} or {@link URL}) to interact with the deployed - * runtime. - */ -@Target(value = ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface RestateURL {} diff --git a/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java b/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java deleted file mode 100644 index cf30ca76..00000000 --- a/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@VirtualObject(name = "Counter") -public class Counter { - - private static final Logger LOG = LogManager.getLogger(Counter.class); - - private static final StateKey TOTAL = StateKey.of("total", CoreSerdes.JSON_LONG); - - @Handler - public void reset(ObjectContext ctx) { - ctx.clearAll(); - } - - @Handler - public void add(ObjectContext ctx, Long request) { - long currentValue = ctx.get(TOTAL).orElse(0L); - long newValue = currentValue + request; - ctx.set(TOTAL, newValue); - } - - @Handler - public long get(ObjectContext ctx) { - return ctx.get(TOTAL).orElse(0L); - } - - @Handler - public CounterUpdateResult getAndAdd(ObjectContext ctx, Long request) { - LOG.info("Invoked get and add with " + request); - - long currentValue = ctx.get(TOTAL).orElse(0L); - long newValue = currentValue + request; - ctx.set(TOTAL, newValue); - - return new CounterUpdateResult(newValue, currentValue); - } - - public static class CounterUpdateResult { - private final Long newValue; - private final Long oldValue; - - public CounterUpdateResult(Long newValue, Long oldValue) { - this.newValue = newValue; - this.oldValue = oldValue; - } - - public Long getNewValue() { - return newValue; - } - - public Long getOldValue() { - return oldValue; - } - } -} diff --git a/sdk-testing/src/test/java/dev/restate/sdk/testing/CounterTest.java b/sdk-testing/src/test/java/dev/restate/sdk/testing/CounterTest.java deleted file mode 100644 index efe6ea9a..00000000 --- a/sdk-testing/src/test/java/dev/restate/sdk/testing/CounterTest.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import static org.assertj.core.api.Assertions.assertThat; - -import dev.restate.sdk.client.IngressClient; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -class CounterTest { - - @RegisterExtension - private static final RestateRunner RESTATE_RUNNER = - RestateRunnerBuilder.create() - .withRestateContainerImage( - "ghcr.io/restatedev/restate:main") // test against the latest main Restate image - .with(new Counter()) - .buildRunner(); - - @Test - void testGreet(@RestateIngressClient IngressClient ingressClient) { - var client = CounterClient.fromIngress(ingressClient, "my-counter"); - long response = client.get(); - - assertThat(response).isEqualTo(0L); - } -} diff --git a/sdk-testing/src/test/proto/counter.proto b/sdk-testing/src/test/proto/counter.proto deleted file mode 100644 index 065e1ac2..00000000 --- a/sdk-testing/src/test/proto/counter.proto +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -syntax = "proto3"; - -package counter; - -import "google/protobuf/empty.proto"; -import "dev/restate/ext.proto"; - -option java_multiple_files = true; -option java_package = "dev.restate.sdk.examples.generated"; -option java_outer_classname = "CounterProto"; - -service Counter { - option (dev.restate.ext.service_type) = KEYED; - - rpc Reset (CounterRequest) returns (google.protobuf.Empty); - rpc Add (CounterAddRequest) returns (google.protobuf.Empty); - rpc Get (CounterRequest) returns (GetResponse); - rpc GetAndAdd (CounterAddRequest) returns (CounterUpdateResult); -} - -message CounterRequest { - string counter_name = 1 [(dev.restate.ext.field) = KEY]; -} - -message CounterAddRequest { - string counter_name = 1 [(dev.restate.ext.field) = KEY]; - int64 value = 2; -} - -message GetResponse { - int64 value = 1; -} - -message CounterUpdateResult { - int64 old_value = 1; - int64 new_value = 2; -} \ No newline at end of file diff --git a/sdk-testing/src/test/resources/log4j2.properties b/sdk-testing/src/test/resources/log4j2.properties deleted file mode 100644 index 130894e5..00000000 --- a/sdk-testing/src/test/resources/log4j2.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Set to debug or trace if log4j initialization is failing -status = warn - -# Console appender configuration -appender.console.type = Console -appender.console.name = consoleLogger -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %notEmpty{[%X{restateServiceMethod}]}%notEmpty{[%X{restateInvocationId}]} %c - %m%n - -# Restate logs to debug level -logger.app.name = dev.restate -logger.app.level = debug -logger.app.additivity = false -logger.app.appenderRef.console.ref = consoleLogger - -# Root logger -rootLogger.level = info -rootLogger.appenderRef.stdout.ref = consoleLogger \ No newline at end of file diff --git a/sdk-workflow-api/build.gradle.kts b/sdk-workflow-api/build.gradle.kts deleted file mode 100644 index 84f28a0a..00000000 --- a/sdk-workflow-api/build.gradle.kts +++ /dev/null @@ -1,53 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` - alias(pluginLibs.plugins.protobuf) -} - -description = "Restate SDK Workflow APIs" - -dependencies { - compileOnly(coreLibs.jspecify) - - api(project(":sdk-common")) - api(project(":sdk-api")) - - implementation(coreLibs.protobuf.java) - implementation(coreLibs.log4j.core) - - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.annotations) - implementation(jacksonLibs.jackson.jsr310) - implementation(project(":sdk-serde-jackson")) - - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - - // Import test suites from sdk-core - testImplementation(project(":sdk-core", "testArchive")) -} - -// Configure protobuf - -val protobufVersion = coreLibs.versions.protobuf.get() - -protobuf { protoc { artifact = "com.google.protobuf:protoc:$protobufVersion" } } - -// Make sure task dependencies are correct - -tasks { - withType { dependsOn(generateProto) } - withType { dependsOn(generateProto) } -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromise.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromise.java deleted file mode 100644 index 5a2f4a0b..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromise.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.Awaitable; -import java.util.Optional; - -public interface DurablePromise { - Awaitable awaitable(); - - Optional peek(); - - boolean isCompleted(); -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseHandle.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseHandle.java deleted file mode 100644 index b9022180..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseHandle.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -public interface DurablePromiseHandle { - void resolve(T payload) throws IllegalStateException; - - void reject(String reason) throws IllegalStateException; -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseKey.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseKey.java deleted file mode 100644 index 758dead6..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseKey.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Serde; - -/** - * This class holds information about durable promise's name and its type tag to be used for - * serializing and deserializing it. - * - * @param the generic type of the signal. - */ -public final class DurablePromiseKey { - - private final String name; - private final Serde serde; - - private DurablePromiseKey(String name, Serde serde) { - this.name = name; - this.serde = serde; - } - - /** Create a new {@link DurablePromiseKey}. */ - public static DurablePromiseKey of(String name, Serde serde) { - return new DurablePromiseKey<>(name, serde); - } - - /** Create a new {@link DurablePromiseKey} for {@link String} state. */ - public static DurablePromiseKey string(String name) { - return new DurablePromiseKey<>(name, CoreSerdes.JSON_STRING); - } - - /** Create a new {@link DurablePromiseKey} for bytes state. */ - public static DurablePromiseKey raw(String name) { - return new DurablePromiseKey<>(name, CoreSerdes.RAW); - } - - public String name() { - return name; - } - - public Serde serde() { - return serde; - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowBuilder.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowBuilder.java deleted file mode 100644 index 40f54853..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowBuilder.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.Component; -import dev.restate.sdk.common.BindableComponent; -import dev.restate.sdk.workflow.impl.WorkflowImpl; -import java.util.HashMap; -import java.util.function.BiFunction; - -public final class WorkflowBuilder { - - private final String name; - private final Component.Handler workflowMethod; - private final HashMap> sharedMethods; - - private WorkflowBuilder(String name, Component.Handler workflowMethod) { - this.name = name; - this.workflowMethod = workflowMethod; - this.sharedMethods = new HashMap<>(); - } - - public WorkflowBuilder withShared( - Component.HandlerSignature sig, - BiFunction runner) { - this.sharedMethods.put(sig.getName(), new Component.Handler<>(sig, runner)); - return this; - } - - public BindableComponent build() { - return new WorkflowImpl(name, workflowMethod, sharedMethods); - } - - public static WorkflowBuilder named( - String name, - Component.HandlerSignature sig, - BiFunction runner) { - return new WorkflowBuilder(name, new Component.Handler<>(sig, runner)); - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowContext.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowContext.java deleted file mode 100644 index ff49578b..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowContext.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.common.StateKey; -import org.jspecify.annotations.NonNull; - -public interface WorkflowContext extends WorkflowSharedContext { - - void clear(StateKey key); - - void set(StateKey key, @NonNull T value); -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowExecutionState.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowExecutionState.java deleted file mode 100644 index 0dc06b41..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowExecutionState.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -public enum WorkflowExecutionState { - STARTED, - ALREADY_STARTED, - ALREADY_COMPLETED, -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowSharedContext.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowSharedContext.java deleted file mode 100644 index df17fe30..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowSharedContext.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.Context; -import dev.restate.sdk.common.StateKey; -import java.util.Optional; - -public interface WorkflowSharedContext extends Context { - - String workflowKey(); - - Optional get(StateKey key); - - // -- Signals - - DurablePromise durablePromise(DurablePromiseKey key); - - DurablePromiseHandle durablePromiseHandle(DurablePromiseKey key); -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/InvokeRequest.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/InvokeRequest.java deleted file mode 100644 index 649ee5c7..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/InvokeRequest.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow.impl; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.Objects; - -public final class InvokeRequest { - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private final String key; - private final JsonNode payload; - - @JsonCreator - public InvokeRequest(@JsonProperty("key") String key, @JsonProperty("payload") JsonNode payload) { - this.key = key; - this.payload = payload; - } - - public String getKey() { - return key; - } - - public JsonNode getPayload() { - return payload; - } - - public static InvokeRequest fromAny(String key, Object value) { - return new InvokeRequest(key, OBJECT_MAPPER.convertValue(value, JsonNode.class)); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - InvokeRequest that = (InvokeRequest) o; - return Objects.equals(key, that.key) && Objects.equals(payload, that.payload); - } - - @Override - public int hashCode() { - return Objects.hash(key, payload); - } - - @Override - public String toString() { - return "InvokeRequest{" + "key='" + key + '\'' + ", payload=" + payload + '}'; - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java deleted file mode 100644 index aa068616..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow.impl; - -import static dev.restate.sdk.workflow.impl.WorkflowImpl.workflowManagerObjectName; - -import dev.restate.sdk.Awaitable; -import dev.restate.sdk.Context; -import dev.restate.sdk.client.IngressClient; -import dev.restate.sdk.common.*; -import dev.restate.sdk.workflow.WorkflowExecutionState; -import dev.restate.sdk.workflow.generated.GetOutputResponse; -import dev.restate.sdk.workflow.generated.GetStateResponse; -import java.time.Duration; -import java.util.Optional; -import org.jspecify.annotations.Nullable; - -// Methods invoked from code-generated classes -public final class WorkflowCodegenUtil { - - private WorkflowCodegenUtil() {} - - // -- Restate client methods - - public static class RestateClient { - private RestateClient() {} - - public static Awaitable submit( - Context ctx, String workflowName, String workflowKey, @Nullable Object payload) { - return ctx.call( - Target.service(workflowName, "submit"), - WorkflowImpl.INVOKE_REQUEST_SERDE, - WorkflowImpl.WORKFLOW_EXECUTION_STATE_SERDE, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static Awaitable> getOutput( - Context ctx, String workflowName, String workflowKey, Serde serde) { - return ctx.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getOutput"), - CoreSerdes.VOID, - WorkflowImpl.GET_OUTPUT_RESPONSE_SERDE, - null) - .map( - response -> { - if (response.hasNotCompleted()) { - return Optional.empty(); - } - if (response.hasFailure()) { - throw new TerminalException( - TerminalException.Code.fromValue(response.getFailure().getCode()), - response.getFailure().getMessage()); - } - return Optional.ofNullable(serde.deserialize(response.getValue())); - }); - } - - public static Awaitable isCompleted( - Context ctx, String workflowName, String workflowKey) { - return ctx.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getOutput"), - CoreSerdes.VOID, - WorkflowImpl.GET_OUTPUT_RESPONSE_SERDE, - null) - .map( - response -> { - if (response.hasFailure()) { - throw new TerminalException( - TerminalException.Code.fromValue(response.getFailure().getCode()), - response.getFailure().getMessage()); - } - return !response.hasNotCompleted(); - }); - } - - public static Awaitable invokeShared( - Context ctx, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload, - Serde resSerde) { - return ctx.call( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - resSerde, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static void invokeSharedSend( - Context ctx, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload) { - ctx.send( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static void invokeSharedSendDelayed( - Context ctx, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload, - Duration delay) { - ctx.sendDelayed( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - InvokeRequest.fromAny(workflowKey, payload), - delay); - } - - public static Awaitable> getState( - Context ctx, String workflowName, String workflowKey, StateKey key) { - return ctx.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getState"), - CoreSerdes.JSON_STRING, - WorkflowImpl.GET_STATE_RESPONSE_SERDE, - key.name()) - .map( - response -> { - if (response.hasEmpty()) { - return Optional.empty(); - } - return Optional.of(key.serde().deserialize(response.getValue())); - }); - } - } - - // --- External client methods - - public static class ExternalClient { - private ExternalClient() {} - - public static WorkflowExecutionState submit( - IngressClient ingressClient, - String workflowName, - String workflowKey, - @Nullable Object payload) { - return ingressClient.call( - Target.service(workflowName, "submit"), - WorkflowImpl.INVOKE_REQUEST_SERDE, - WorkflowImpl.WORKFLOW_EXECUTION_STATE_SERDE, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static Optional getOutput( - IngressClient ingressClient, String workflowName, String workflowKey, Serde serde) { - GetOutputResponse response = - ingressClient.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getOutput"), - CoreSerdes.VOID, - WorkflowImpl.GET_OUTPUT_RESPONSE_SERDE, - null); - if (response.hasNotCompleted()) { - return Optional.empty(); - } - if (response.hasFailure()) { - throw new TerminalException( - TerminalException.Code.fromValue(response.getFailure().getCode()), - response.getFailure().getMessage()); - } - return Optional.ofNullable(serde.deserialize(response.getValue())); - } - - public static boolean isCompleted( - IngressClient ingressClient, String workflowName, String workflowKey) { - GetOutputResponse response = - ingressClient.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getOutput"), - CoreSerdes.VOID, - WorkflowImpl.GET_OUTPUT_RESPONSE_SERDE, - null); - if (response.hasFailure()) { - throw new TerminalException( - TerminalException.Code.fromValue(response.getFailure().getCode()), - response.getFailure().getMessage()); - } - return !response.hasNotCompleted(); - } - - public static T invokeShared( - IngressClient ingressClient, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload, - Serde resSerde) { - return ingressClient.call( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - resSerde, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static void invokeSharedSend( - IngressClient ingressClient, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload) { - ingressClient.send( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static Optional getState( - IngressClient ingressClient, String workflowName, String workflowKey, StateKey key) { - GetStateResponse response = - ingressClient.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getState"), - CoreSerdes.JSON_STRING, - WorkflowImpl.GET_STATE_RESPONSE_SERDE, - key.name()); - if (response.hasEmpty()) { - return Optional.empty(); - } - return Optional.of(key.serde().deserialize(response.getValue())); - } - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java deleted file mode 100644 index 63d19344..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow.impl; - -import static dev.restate.sdk.workflow.impl.WorkflowImpl.workflowManagerObjectName; - -import dev.restate.sdk.*; -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.function.ThrowingRunnable; -import dev.restate.sdk.common.function.ThrowingSupplier; -import dev.restate.sdk.workflow.DurablePromise; -import dev.restate.sdk.workflow.DurablePromiseHandle; -import dev.restate.sdk.workflow.DurablePromiseKey; -import dev.restate.sdk.workflow.WorkflowContext; -import dev.restate.sdk.workflow.generated.*; -import java.time.Duration; -import java.util.Optional; -import org.jspecify.annotations.NonNull; - -class WorkflowContextImpl implements WorkflowContext { - - private final Context ctx; - private final String workflowFsqn; - private final String workflowKey; - private final boolean isExclusive; - - WorkflowContextImpl(Context ctx, String workflowFqsn, String workflowKey, boolean isExclusive) { - this.ctx = ctx; - this.workflowFsqn = workflowFqsn; - this.workflowKey = workflowKey; - this.isExclusive = isExclusive; - } - - @Override - public String workflowKey() { - return this.workflowKey; - } - - // --- State ops - - @Override - public Optional get(StateKey key) { - GetStateResponse response = - this.ctx - .call( - workflowManagerTarget("getState"), - CoreSerdes.JSON_STRING, - WorkflowImpl.GET_STATE_RESPONSE_SERDE, - key.name()) - .await(); - - switch (response.getResultCase()) { - case VALUE: - return Optional.of(key.serde().deserialize(response.getValue())); - case EMPTY: - return Optional.empty(); - } - throw new IllegalStateException("Unexpected response from WorkflowManager"); - } - - @Override - public void clear(StateKey key) { - if (!isExclusive) { - throw new UnsupportedOperationException("Can't perform a state update on a SharedContext"); - } - this.ctx.send(workflowManagerTarget("clearState"), CoreSerdes.JSON_STRING, key.name()); - } - - @Override - public void set(StateKey key, @NonNull T value) { - if (!isExclusive) { - throw new UnsupportedOperationException("Can't perform a state update on a SharedContext"); - } - this.ctx.send( - workflowManagerTarget("setState"), - WorkflowImpl.SET_STATE_REQUEST_SERDE, - SetStateRequest.newBuilder() - .setStateKey(key.name()) - .setStateValue(key.serde().serializeToByteString(value)) - .build()); - } - - // -- Signal - - @Override - public DurablePromise durablePromise(DurablePromiseKey key) { - Awakeable awakeable = ctx.awakeable(key.serde()); - - // Register durablePromise - ctx.send( - workflowManagerTarget("waitDurablePromiseCompletion"), - WorkflowImpl.WAIT_DURABLE_PROMISE_COMPLETION_REQUEST_SERDE, - WaitDurablePromiseCompletionRequest.newBuilder() - .setDurablePromiseKey(key.name()) - .setAwakeableId(awakeable.id()) - .build()); - - return new DurablePromise<>() { - @Override - public Awaitable awaitable() { - return awakeable; - } - - @Override - public Optional peek() { - MaybeDurablePromiseCompletion maybeDurablePromiseCompletion = - ctx.call( - workflowManagerTarget("getDurablePromiseCompletion"), - CoreSerdes.JSON_STRING, - WorkflowImpl.MAYBE_DURABLE_PROMISE_COMPLETION_SERDE, - key.name()) - .await(); - - switch (maybeDurablePromiseCompletion.getResultCase()) { - case VALUE: - return Optional.of(key.serde().deserialize(maybeDurablePromiseCompletion.getValue())); - case FAILURE: - throw new TerminalException( - TerminalException.Code.fromValue( - maybeDurablePromiseCompletion.getFailure().getCode()), - maybeDurablePromiseCompletion.getFailure().getMessage()); - case NOT_COMPLETED: - return Optional.empty(); - } - throw new IllegalStateException("Unexpected response from WorkflowManager"); - } - - @Override - public boolean isCompleted() { - MaybeDurablePromiseCompletion maybeDurablePromiseCompletion = - ctx.call( - workflowManagerTarget("getDurablePromiseCompletion"), - CoreSerdes.JSON_STRING, - WorkflowImpl.MAYBE_DURABLE_PROMISE_COMPLETION_SERDE, - key.name()) - .await(); - return !maybeDurablePromiseCompletion.hasNotCompleted(); - } - }; - } - - @Override - public DurablePromiseHandle durablePromiseHandle(DurablePromiseKey key) { - return new DurablePromiseHandle<>() { - @Override - public void resolve(T payload) throws IllegalStateException { - ctx.send( - workflowManagerTarget("completeDurablePromise"), - WorkflowImpl.COMPLETE_DURABLE_PROMISE_REQUEST_SERDE, - CompleteDurablePromiseRequest.newBuilder() - .setDurablePromiseKey(key.name()) - .setCompletion( - DurablePromiseCompletion.newBuilder() - .setValue(key.serde().serializeToByteString(payload))) - .build()); - } - - @Override - public void reject(String reason) throws IllegalStateException { - ctx.send( - workflowManagerTarget("completeDurablePromise"), - WorkflowImpl.COMPLETE_DURABLE_PROMISE_REQUEST_SERDE, - CompleteDurablePromiseRequest.newBuilder() - .setDurablePromiseKey(key.name()) - .setCompletion( - DurablePromiseCompletion.newBuilder() - .setFailure(Failure.newBuilder().setCode(2).setMessage(reason))) - .build()); - } - }; - } - - // -- Delegates to RestateContext - - @Override - public void sleep(Duration duration) { - ctx.sleep(duration); - } - - @Override - public Awaitable timer(Duration duration) { - return ctx.timer(duration); - } - - @Override - public InvocationId invocationId() { - return this.ctx.invocationId(); - } - - @Override - public Awaitable call( - Target target, Serde inputSerde, Serde outputSerde, T parameter) { - return ctx.call(target, inputSerde, outputSerde, parameter); - } - - @Override - public void send(Target target, Serde inputSerde, T parameter) { - ctx.send(target, inputSerde, parameter); - } - - @Override - public void sendDelayed(Target target, Serde inputSerde, T parameter, Duration delay) { - ctx.sendDelayed(target, inputSerde, parameter, delay); - } - - @Override - public T sideEffect(Serde serde, ThrowingSupplier action) throws TerminalException { - return ctx.sideEffect(serde, action); - } - - @Override - public void sideEffect(ThrowingRunnable runnable) throws TerminalException { - ctx.sideEffect(runnable); - } - - @Override - public Awakeable awakeable(Serde serde) { - return ctx.awakeable(serde); - } - - @Override - public AwakeableHandle awakeableHandle(String id) { - return ctx.awakeableHandle(id); - } - - @Override - public RestateRandom random() { - return ctx.random(); - } - - private Target workflowManagerTarget(String handler) { - return Target.virtualObject( - workflowManagerObjectName(this.workflowFsqn), this.workflowKey, handler); - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java deleted file mode 100644 index 843a3a60..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow.impl; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.google.protobuf.*; -import dev.restate.sdk.Component; -import dev.restate.sdk.Component.HandlerSignature; -import dev.restate.sdk.Context; -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.syscalls.ComponentDefinition; -import dev.restate.sdk.serde.jackson.JacksonSerdes; -import dev.restate.sdk.workflow.WorkflowContext; -import dev.restate.sdk.workflow.WorkflowExecutionState; -import dev.restate.sdk.workflow.generated.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.function.BiFunction; -import org.jspecify.annotations.Nullable; - -public class WorkflowImpl implements BindableComponent { - - public static final Serde INVOKE_REQUEST_SERDE = - JacksonSerdes.of(InvokeRequest.class); - static final Serde WORKFLOW_EXECUTION_STATE_SERDE = - JacksonSerdes.of(WorkflowExecutionState.class); - static final Serde GET_STATE_RESPONSE_SERDE = - CoreSerdes.ofProtobuf(GetStateResponse.parser()); - static final Serde SET_STATE_REQUEST_SERDE = - CoreSerdes.ofProtobuf(SetStateRequest.parser()); - static final Serde - WAIT_DURABLE_PROMISE_COMPLETION_REQUEST_SERDE = - CoreSerdes.ofProtobuf(WaitDurablePromiseCompletionRequest.parser()); - static final Serde MAYBE_DURABLE_PROMISE_COMPLETION_SERDE = - CoreSerdes.ofProtobuf(MaybeDurablePromiseCompletion.parser()); - static final Serde COMPLETE_DURABLE_PROMISE_REQUEST_SERDE = - CoreSerdes.ofProtobuf(CompleteDurablePromiseRequest.parser()); - static final Serde GET_OUTPUT_RESPONSE_SERDE = - CoreSerdes.ofProtobuf(GetOutputResponse.parser()); - private static final Serde SET_OUTPUT_REQUEST_SERDE = - CoreSerdes.ofProtobuf(SetOutputRequest.parser()); - private static final Serde DURABLE_PROMISE_COMPLETION_SERDE = - CoreSerdes.ofProtobuf(DurablePromiseCompletion.parser()); - - private static final Serde> DURABLEPROMISE_LISTENER_SERDE = - JacksonSerdes.of(new TypeReference<>() {}); - private static final StateKey OUTPUT_KEY = - StateKey.of("_output", CoreSerdes.ofProtobuf(MethodOutput.parser())); - - private static final StateKey WORKFLOW_EXECUTION_STATE_KEY = - StateKey.of("_workflow_execution_state", WORKFLOW_EXECUTION_STATE_SERDE); - private static final String START_HANDLER = "_start"; - - private final String name; - private final Component.Handler workflowMethod; - private final HashMap> sharedHandlers; - - public WorkflowImpl( - String name, - Component.Handler workflowMethod, - HashMap> sharedHandlers) { - this.name = name; - this.workflowMethod = workflowMethod; - this.sharedHandlers = sharedHandlers; - } - - // --- Workflow methods - - private WorkflowExecutionState submit(Context objectContext, InvokeRequest invokeRequest) { - // Try start - var response = - objectContext - .call( - workflowManagerTarget(invokeRequest.getKey(), "tryStart"), - CoreSerdes.JSON_STRING, - WORKFLOW_EXECUTION_STATE_SERDE, - invokeRequest.getKey()) - .await(); - if (response.equals(WorkflowExecutionState.STARTED)) { - // Schedule start - objectContext.send( - Target.service(name, WorkflowImpl.START_HANDLER), INVOKE_REQUEST_SERDE, invokeRequest); - } - - return response; - } - - private void internalStart(Context context, InvokeRequest invokeRequest) { - // We can start now! - byte[] valueOutput; - try { - // Convert input - Object input = - this.workflowMethod - .getHandlerSignature() - .getRequestSerde() - .deserialize(invokeRequest.getPayload().toString().getBytes(StandardCharsets.UTF_8)); - - // Invoke run - WorkflowContext ctx = new WorkflowContextImpl(context, name, invokeRequest.getKey(), true); - @SuppressWarnings("unchecked") - Object output = - ((BiFunction) this.workflowMethod.getRunner()).apply(ctx, input); - - //noinspection unchecked - valueOutput = - ((Serde) this.workflowMethod.getHandlerSignature().getResponseSerde()) - .serialize(output); - } catch (TerminalException e) { - // Intercept TerminalException to record it - context.send( - workflowManagerTarget(invokeRequest.getKey(), "setOutput"), - SET_OUTPUT_REQUEST_SERDE, - SetOutputRequest.newBuilder() - .setOutput( - MethodOutput.newBuilder() - .setFailure( - Failure.newBuilder() - .setCode(e.getCode().value()) - .setMessage(e.getMessage()))) - .build()); - throw e; - } - - // Record output - context.send( - workflowManagerTarget(invokeRequest.getKey(), "setOutput"), - SET_OUTPUT_REQUEST_SERDE, - SetOutputRequest.newBuilder() - .setOutput( - MethodOutput.newBuilder().setValue(UnsafeByteOperations.unsafeWrap(valueOutput))) - .build()); - } - - private byte[] invokeSharedMethod(String handlerName, Context context, InvokeRequest request) { - // Lookup the method - @SuppressWarnings("unchecked") - Component.Handler method = - (Component.Handler) sharedHandlers.get(handlerName); - if (method == null) { - throw new TerminalException( - TerminalException.Code.NOT_FOUND, "Method " + handlerName + " not found"); - } - - // Convert input - Object input = - method - .getHandlerSignature() - .getRequestSerde() - .deserialize(request.getPayload().toString().getBytes(StandardCharsets.UTF_8)); - - // Invoke method - WorkflowContext ctx = new WorkflowContextImpl(context, name, request.getKey(), false); - // We let the sdk core to manage the failures - Object output = method.getRunner().apply(ctx, input); - - return method.getHandlerSignature().getResponseSerde().serialize(output); - } - - // --- Workflow manager methods - - private GetStateResponse getState(ObjectContext context, String key) throws TerminalException { - return context - .get(stateKey(key)) - .map(val -> GetStateResponse.newBuilder().setValue(val).build()) - .orElseGet( - () -> GetStateResponse.newBuilder().setEmpty(Empty.getDefaultInstance()).build()); - } - - private void setState(ObjectContext context, SetStateRequest request) throws TerminalException { - context.set(stateKey(request.getStateKey()), request.getStateValue()); - } - - private void clearState(ObjectContext context, String key) throws TerminalException { - context.clear(stateKey(key)); - } - - private void waitDurablePromiseCompletion( - ObjectContext context, WaitDurablePromiseCompletionRequest request) throws TerminalException { - Optional val = - context.get(durablePromiseKey(request.getDurablePromiseKey())); - if (val.isPresent()) { - completeListener(context, request.getAwakeableId(), val.get()); - return; - } - - StateKey> listenersKey = durablePromiseListenersKey(request.getDurablePromiseKey()); - Set listeners = context.get(listenersKey).orElseGet(HashSet::new); - listeners.add(request.getAwakeableId()); - context.set(listenersKey, listeners); - } - - private MaybeDurablePromiseCompletion getDurablePromiseCompletion( - ObjectContext context, String durablePromiseKeyStr) throws TerminalException { - StateKey durablePromiseKey = durablePromiseKey(durablePromiseKeyStr); - Optional val = context.get(durablePromiseKey); - if (val.isEmpty()) { - return MaybeDurablePromiseCompletion.newBuilder() - .setNotCompleted(Empty.getDefaultInstance()) - .build(); - } - if (val.get().hasValue()) { - return MaybeDurablePromiseCompletion.newBuilder().setValue(val.get().getValue()).build(); - } - return MaybeDurablePromiseCompletion.newBuilder().setFailure(val.get().getFailure()).build(); - } - - private void completeDurablePromise(ObjectContext context, CompleteDurablePromiseRequest request) - throws TerminalException { - // User can decide whether they want to allow overwriting the previously resolved value or not - StateKey durablePromiseKey = - durablePromiseKey(request.getDurablePromiseKey()); - Optional val = context.get(durablePromiseKey); - if (val.isPresent()) { - throw new TerminalException("Can't complete an already completed durablePromise"); - } - context.set(durablePromiseKey, request.getCompletion()); - - StateKey> listenersKey = durablePromiseListenersKey(request.getDurablePromiseKey()); - Set listeners = context.get(listenersKey).orElse(Collections.emptySet()); - for (String listener : listeners) { - completeListener(context, listener, request.getCompletion()); - } - context.clear(listenersKey); - } - - private WorkflowExecutionState tryStart(ObjectContext context) throws TerminalException { - Optional maybeResponse = context.get(WORKFLOW_EXECUTION_STATE_KEY); - if (maybeResponse.isPresent()) { - return maybeResponse.get(); - } - - context.set(WORKFLOW_EXECUTION_STATE_KEY, WorkflowExecutionState.ALREADY_STARTED); - return WorkflowExecutionState.STARTED; - } - - private GetOutputResponse getOutput(ObjectContext context) throws TerminalException { - return context - .get(OUTPUT_KEY) - .map( - methodOutput -> - methodOutput.hasValue() - ? GetOutputResponse.newBuilder().setValue(methodOutput.getValue()).build() - : GetOutputResponse.newBuilder().setFailure(methodOutput.getFailure()).build()) - .orElseGet( - () -> - GetOutputResponse.newBuilder().setNotCompleted(Empty.getDefaultInstance()).build()); - } - - private void setOutput(ObjectContext context, SetOutputRequest request) throws TerminalException { - context.set(OUTPUT_KEY, request.getOutput()); - context.set(WORKFLOW_EXECUTION_STATE_KEY, WorkflowExecutionState.ALREADY_COMPLETED); - } - - private void cleanup(ObjectContext context) throws TerminalException { - context.clearAll(); - } - - // --- Util methods for WorkflowManager - - private StateKey stateKey(String key) { - return StateKey.of( - "_state_" + key, - new Serde<>() { - @Override - public byte[] serialize(@Nullable ByteString value) { - return value.toByteArray(); - } - - @Override - public ByteString serializeToByteString(@Nullable ByteString value) { - return value; - } - - @Override - public ByteString deserialize(ByteString byteString) { - return byteString; - } - - @Override - public ByteString deserialize(byte[] value) { - return UnsafeByteOperations.unsafeWrap(value); - } - }); - } - - private void completeListener( - ObjectContext context, String listener, DurablePromiseCompletion completion) { - if (completion.hasValue()) { - context - .awakeableHandle(listener) - .resolve(CoreSerdes.RAW, completion.getValue().toByteArray()); - } else { - context.awakeableHandle(listener).reject(completion.getFailure().getMessage()); - } - } - - private StateKey durablePromiseKey(String key) { - return StateKey.of("_durablePromise_" + key, DURABLE_PROMISE_COMPLETION_SERDE); - } - - private StateKey> durablePromiseListenersKey(String key) { - return StateKey.of("_durablePromise_listeners_" + key, DURABLEPROMISE_LISTENER_SERDE); - } - - static String workflowManagerObjectName(String workflowName) { - return workflowName + "_Manager"; - } - - private Target workflowManagerTarget(String key, String handler) { - return Target.virtualObject(workflowManagerObjectName(name), key, handler); - } - - // --- Components definition - - @Override - public List definitions() { - // Prepare workflow service - Component.ServiceBuilder workflowBuilder = - Component.service(name) - .with( - HandlerSignature.of("submit", INVOKE_REQUEST_SERDE, WORKFLOW_EXECUTION_STATE_SERDE), - this::submit) - .with( - HandlerSignature.of(START_HANDLER, INVOKE_REQUEST_SERDE, CoreSerdes.VOID), - (context, invokeRequest) -> { - this.internalStart(context, invokeRequest); - return null; - }); - - // Append shared methods - for (var sharedMethod : sharedHandlers.values()) { - workflowBuilder.with( - HandlerSignature.of( - sharedMethod.getHandlerSignature().getName(), INVOKE_REQUEST_SERDE, CoreSerdes.RAW), - (context, invokeRequest) -> - this.invokeSharedMethod( - sharedMethod.getHandlerSignature().getName(), context, invokeRequest)); - } - - // Prepare workflow manager service - Component workflowManager = - Component.virtualObject(workflowManagerObjectName(name)) - .with( - HandlerSignature.of("getState", CoreSerdes.JSON_STRING, GET_STATE_RESPONSE_SERDE), - this::getState) - .with( - HandlerSignature.of("setState", SET_STATE_REQUEST_SERDE, CoreSerdes.VOID), - (context, setStateRequest) -> { - this.setState(context, setStateRequest); - return null; - }) - .with( - HandlerSignature.of("clearState", CoreSerdes.JSON_STRING, CoreSerdes.VOID), - (context, s) -> { - this.clearState(context, s); - return null; - }) - .with( - HandlerSignature.of( - "waitDurablePromiseCompletion", - WAIT_DURABLE_PROMISE_COMPLETION_REQUEST_SERDE, - CoreSerdes.VOID), - (context, waitDurablePromiseCompletionRequest) -> { - this.waitDurablePromiseCompletion(context, waitDurablePromiseCompletionRequest); - return null; - }) - .with( - HandlerSignature.of( - "getDurablePromiseCompletion", - CoreSerdes.JSON_STRING, - MAYBE_DURABLE_PROMISE_COMPLETION_SERDE), - this::getDurablePromiseCompletion) - .with( - HandlerSignature.of( - "completeDurablePromise", - COMPLETE_DURABLE_PROMISE_REQUEST_SERDE, - CoreSerdes.VOID), - (context, completeDurablePromiseRequest) -> { - this.completeDurablePromise(context, completeDurablePromiseRequest); - return null; - }) - .with( - HandlerSignature.of("tryStart", CoreSerdes.VOID, WORKFLOW_EXECUTION_STATE_SERDE), - (context, unused) -> this.tryStart(context)) - .with( - HandlerSignature.of("getOutput", CoreSerdes.VOID, GET_OUTPUT_RESPONSE_SERDE), - (context, unused) -> this.getOutput(context)) - .with( - HandlerSignature.of("setOutput", SET_OUTPUT_REQUEST_SERDE, CoreSerdes.VOID), - (context, setOutputRequest) -> { - this.setOutput(context, setOutputRequest); - return null; - }) - .with( - HandlerSignature.of("cleanup", CoreSerdes.VOID, CoreSerdes.VOID), - (context, unused) -> { - this.cleanup(context); - return null; - }) - .build(); - - return List.of( - workflowBuilder.build().definitions().get(0), workflowManager.definitions().get(0)); - } -} diff --git a/sdk-workflow-api/src/main/proto/dev/restate/sdk/workflow/workflow.proto b/sdk-workflow-api/src/main/proto/dev/restate/sdk/workflow/workflow.proto deleted file mode 100644 index 4fa7390b..00000000 --- a/sdk-workflow-api/src/main/proto/dev/restate/sdk/workflow/workflow.proto +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -syntax = "proto3"; - -package dev.restate.sdk.workflow; - -import "google/protobuf/empty.proto"; -import "google/protobuf/struct.proto"; - -option java_multiple_files = true; -option java_package = "dev.restate.sdk.workflow.generated"; -option java_outer_classname = "WorkflowProto"; - -message GetStateResponse { - oneof result { - bytes value = 1; - google.protobuf.Empty empty = 2; - } -} - -message SetStateRequest { - string state_key = 2; - bytes state_value = 3; -} - -message GetOutputResponse { - oneof result { - bytes value = 1; - Failure failure = 2; - google.protobuf.Empty not_completed = 3; - }; -} - -message SetOutputRequest { - MethodOutput output = 2; -} - -message WaitDurablePromiseCompletionRequest { - string durable_promise_key = 2; - string awakeable_id = 3; -} - -message DurablePromiseCompletion { - oneof result { - bytes value = 1; - Failure failure = 2; - }; -} - -message MaybeDurablePromiseCompletion { - oneof result { - bytes value = 1; - Failure failure = 2; - google.protobuf.Empty not_completed = 3; - }; -} - -message CompleteDurablePromiseRequest { - string durable_promise_key = 2; - DurablePromiseCompletion completion = 3; -} - -message MethodOutput { - oneof result { - bytes value = 1; - Failure failure = 2; - } -} - -message Failure { - uint32 code = 1; - string message = 2; -} \ No newline at end of file diff --git a/sdk-core/src/main/service-protocol/service-invocation-protocol.md b/service-invocation-protocol.md similarity index 89% rename from sdk-core/src/main/service-protocol/service-invocation-protocol.md rename to service-invocation-protocol.md index 7aef70e1..17278c08 100644 --- a/sdk-core/src/main/service-protocol/service-invocation-protocol.md +++ b/service-invocation-protocol.md @@ -11,7 +11,7 @@ The system is composed of two actors: - SDK, which contains the implementation of the Restate Protocol - User business logic, which interacts with the SDK to access Restate system calls (or syscalls) -Each service method invocation is modeled by the protocol as a state machine, where state transitions can be caused +Each invocation is modeled by the protocol as a state machine, where state transitions can be caused either by user code or by _Runtime events_. Every state transition is logged in the _Invocation journal_, used to implement Restate's durable execution model. The @@ -32,11 +32,12 @@ The state machine is summarized in the following diagram: ```mermaid sequenceDiagram Note over Runtime,SDK: Start - Runtime->>SDK: HTTP Request to /invoke/{service}/{method} + Runtime->>SDK: HTTP Request to /invoke/{service}/{handler} Runtime->>SDK: StartMessage Note over Runtime,SDK: Replaying Runtime->>SDK: [...]EntryMessage(s) Note over Runtime,SDK: Processing + SDK->>Runtime: HTTP Response headers loop SDK->>Runtime: [...]EntryMessage Runtime->>SDK: CompletionMessage and/or EntryAckMessage @@ -102,9 +103,7 @@ protocol mandates the following messages: ### Message stream -In order to execute a service method invocation, service deployment and restate Runtime open a single stream between the -runtime and the service deployment. Given 10 concurrent service method invocations to a service deployment, there are 10 -concurrent streams, each of them mapping to a specific invocation. +In order to execute an invocation, service deployment and restate Runtime open a single stream between the runtime and the service deployment. Given 10 concurrent invocations to a service deployment, there are 10 concurrent streams, each of them mapping to a specific invocation. Every unit of the stream contains a Message serialized using the [Protobuf encoding](https://protobuf.dev/programming-guides/encoding/), using the definitions in @@ -119,10 +118,23 @@ in two modes: runtime. Once the service deployment starts sending messages to the runtime, the runtime cannot send messages anymore back to the service deployment. -When opening the stream, the request method MUST be `POST` and the request path MUST have the following format: +A message stream MUST start with `StartMessage` and MUST end with either: + +- One [`SuspensionMessage`](#suspension) +- One [`ErrorMessage`](#failures) +- One `EndMessage` + +If the message stream does not end with any of these two messages, it will be considered equivalent to sending an +`ErrorMessage` with an [unknown failure](#failures). + +The `EndMessage` marks the end of the invocation lifecycle, that is the end of the journal. + +### Initiating the stream + +When opening the stream, the HTTP request method MUST be `POST` and the request path MUST have the following format: ``` -/invoke/{fullyQualifiedServiceName}/{methodName} +/invoke/{serviceName}/{handlerName} ``` For example: @@ -133,19 +145,29 @@ For example: An arbitrary path MAY prepend the aforementioned path format. -In case the path format is not respected, or `fullyQualifiedServiceName` or `methodName` is unknown, the SDK MUST close -the stream replying back with a `404` status code. +In case the path format is not respected, or `serviceName` or `handlerName` is unknown, the SDK MUST close the stream replying back with a `404` status code. -A message stream MUST start with `StartMessage` and MUST end with either: +In case the invocation is accepted, `200` status code MUST be returned. -- One [`SuspensionMessage`](#suspension) -- One [`ErrorMessage`](#failures) -- One `EndMessage` +Additionally, the header `x-restate-user-agent` MAY be sent back, with the following format: -If the message stream does not end with any of these two messages, it will be considered equivalent to sending an -`ErrorMessage` with an [unknown failure](#failures). +```http request +x-restate-user-agent: / ; +``` -The `EndMessage` marks the end of the invocation lifecycle, that is the end of the journal. +E.g.: + +```http request +x-restate-user-agent: restate-sdk-java/0.8.0 +``` + +Or: + +```http request +x-restate-user-agent: restate-sdk-java/0.8.0; gitHash=0c5917b +``` + +This header is used for observability purposes by the Restate observability tools. ### Message header @@ -275,6 +297,11 @@ index of the corresponding entry. | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +#### Entry names + +Every Journal entry has a field `string name = 12`, which can be set by the SDK when recording the entry. This field is +used for observability purposes by Restate observability tools. + ### Journal entries reference The following tables describe the currently available journal entries. For more details, check the protobuf message @@ -365,6 +392,11 @@ additional features to the users. The protocol allows the SDK to register an arbitrary entry type within the journal. The type MUST be `>= 0xFC00`. The runtime will treat this entry as any other entry, persisting it and sending it during replay in the correct order. +Custom entries MAY have the entry name field `12`, as described in [entry names](#entry-names). + +The field numbers 13, 14 and 15 MUST not be used, as they're reserved for completable journal entries, as described in +[completable journal entries](#completable-journal-entries-and-completionmessage). + **Header** 0 1 2 3 @@ -405,3 +437,15 @@ A possible implementation could be the following. Given a user requests a state In order for the aforementioned algorithm to work, set, clear and clear all state operations must be reflected on the local `state_map` as well. + +### Side effect/Run + +The side effect/run feature allows users to execute arbitrary non-deterministic code within their service and record the +result, such that on re-executions the stored value will be used instead of replaying the given code. + +SDKs MAY implement the side effect feature by storing the result using a custom entry message, as described in +[Custom entry](#custom-entry-messages). By convention, the SDKs SHOULD use the entry type `FC01` for side effects. + +When storing side effects, SDKs MAY need to wait for the +[acknowledgment of the stored entry](#acknowledgment-of-stored-entries) before continuing the execution of the +invocation. diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index b9f9d1d4..00000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/7.4.2/userguide/multi_project_builds.html - */ - -rootProject.name = "sdk-java" - -plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0" } - -include( - "admin-client", - "sdk-common", - "sdk-api", - "sdk-api-kotlin", - "sdk-core", - "sdk-serde-jackson", - "sdk-http-vertx", - "sdk-lambda", - "sdk-testing", - "sdk-api-gen-common", - "sdk-api-gen", - "sdk-api-kotlin-gen", - "sdk-workflow-api", - "examples", -) - -dependencyResolutionManagement { - repositories { mavenCentral() } - - versionCatalogs { - create("coreLibs") { - version("protobuf", "3.24.3") - version("log4j", "2.22.0") - version("opentelemetry", "1.30.1") - - library("protoc", "com.google.protobuf", "protoc").versionRef("protobuf") - library("protobuf-java", "com.google.protobuf", "protobuf-java").versionRef("protobuf") - library("protobuf-kotlin", "com.google.protobuf", "protobuf-kotlin").versionRef("protobuf") - - library("log4j-api", "org.apache.logging.log4j", "log4j-api").versionRef("log4j") - library("log4j-core", "org.apache.logging.log4j", "log4j-core").versionRef("log4j") - - library("opentelemetry-bom", "io.opentelemetry", "opentelemetry-bom") - .versionRef("opentelemetry") - library("opentelemetry-api", "io.opentelemetry", "opentelemetry-api").withoutVersion() - library("opentelemetry-semconv", "io.opentelemetry:opentelemetry-semconv:1.19.0-alpha") - - library("jspecify", "org.jspecify", "jspecify").version("0.3.0") - } - create("vertxLibs") { - library("vertx-bom", "io.vertx:vertx-stack-depchain:4.5.1") - library("vertx-core", "io.vertx", "vertx-core").withoutVersion() - library("vertx-kotlin-coroutines", "io.vertx", "vertx-lang-kotlin-coroutines") - .withoutVersion() - library("vertx-junit5", "io.vertx", "vertx-junit5").withoutVersion() - } - create("lambdaLibs") { - library("core", "com.amazonaws:aws-lambda-java-core:1.2.2") - library("events", "com.amazonaws:aws-lambda-java-events:3.11.0") - } - create("jacksonLibs") { - version("jackson", "2.16.1") - - library("jackson-bom", "com.fasterxml.jackson", "jackson-bom").versionRef("jackson") - library("jackson-annotations", "com.fasterxml.jackson.core", "jackson-annotations") - .withoutVersion() - library("jackson-core", "com.fasterxml.jackson.core", "jackson-core").withoutVersion() - library("jackson-databind", "com.fasterxml.jackson.core", "jackson-databind").withoutVersion() - library("jackson-jsr310", "com.fasterxml.jackson.datatype", "jackson-datatype-jsr310") - .withoutVersion() - library("jackson-jdk8", "com.fasterxml.jackson.datatype", "jackson-datatype-jdk8") - .withoutVersion() - } - create("kotlinLibs") { - library("kotlinx-coroutines", "org.jetbrains.kotlinx", "kotlinx-coroutines-core") - .version("1.7.3") - library("kotlinx-serialization-core", "org.jetbrains.kotlinx", "kotlinx-serialization-core") - .version("1.6.2") - library("kotlinx-serialization-json", "org.jetbrains.kotlinx", "kotlinx-serialization-json") - .version("1.6.2") - - version("ksp", "1.9.22-1.0.18") - library("symbol-processing-api", "com.google.devtools.ksp", "symbol-processing-api") - .versionRef("ksp") - plugin("ksp", "com.google.devtools.ksp").versionRef("ksp") - } - create("testingLibs") { - version("junit-jupiter", "5.9.1") - version("assertj", "3.23.1") - version("testcontainers", "1.19.4") - - library("junit-jupiter", "org.junit.jupiter", "junit-jupiter").versionRef("junit-jupiter") - library("junit-api", "org.junit.jupiter", "junit-jupiter-api").versionRef("junit-jupiter") - - library("assertj", "org.assertj", "assertj-core").versionRef("assertj") - - library("testcontainers-core", "org.testcontainers", "testcontainers") - .versionRef("testcontainers") - library("testcontainers-toxiproxy", "org.testcontainers", "toxiproxy") - .versionRef("testcontainers") - } - create("pluginLibs") { - plugin("spotless", "com.diffplug.spotless").version("6.22.0") - plugin("protobuf", "com.google.protobuf").version("0.9.4") - plugin("test-logger", "com.adarshr.test-logger").version("4.0.0") - } - } -}