diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index aab88d9d..2168d48d 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -62,7 +62,7 @@ jobs: if: github.event.inputs.sdkJavaVersion != '' with: distribution: "temurin" - java-version: "21" + java-version: "25" - name: Setup Gradle if: github.event.inputs.sdkJavaVersion != '' uses: gradle/actions/setup-gradle@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57f26521..edc704f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,10 +37,7 @@ jobs: java-hello-world-gradle.zip java-hello-world-maven.zip - java-hello-world-new-api-gradle.zip - java-hello-world-new-api-maven.zip java-hello-world-maven-spring-boot.zip - java-hello-world-new-api-maven-spring-boot.zip java-hello-world-maven-quarkus.zip java-hello-world-lambda-cdk.zip java-tour-of-orchestration.zip @@ -145,27 +142,6 @@ jobs: with: source-directory: java/templates/java-maven-spring-boot destination-repo: java-spring-boot-template - copy-java-new-api-maven-template: - name: Copy Java New API Maven template to java-new-api-maven-template repo - uses: ./.github/workflows/copy-to-repo.yml - secrets: inherit - with: - source-directory: java/templates/java-new-api-maven - destination-repo: java-new-api-maven-template - copy-java-new-api-gradle-template: - name: Copy Java New API Gradle template to java-new-api-gradle-template repo - uses: ./.github/workflows/copy-to-repo.yml - secrets: inherit - with: - source-directory: java/templates/java-new-api-gradle - destination-repo: java-new-api-gradle-template - copy-java-new-api-spring-boot-template: - name: Copy Java New API Spring boot template to java-new-api-spring-boot-template repo - uses: ./.github/workflows/copy-to-repo.yml - secrets: inherit - with: - source-directory: java/templates/java-new-api-maven-spring-boot - destination-repo: java-new-api-spring-boot-template copy-kotlin-template: name: Copy Kotlin template to kotlin-template repo uses: ./.github/workflows/copy-to-repo.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d670f78e..c2d4bf8e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: RESTATE_DISABLE_TELEMETRY: "true" strategy: matrix: - jvm-version: [ 21 ] + jvm-version: [ 25 ] steps: - uses: actions/checkout@v3 diff --git a/.tools/prepare_release_zip.sh b/.tools/prepare_release_zip.sh index 9d7e76a9..33a928f2 100755 --- a/.tools/prepare_release_zip.sh +++ b/.tools/prepare_release_zip.sh @@ -34,10 +34,7 @@ create_release_zip java/integrations/java-gradle-lambda-cdk java-hello-world-lam create_release_zip java/patterns-use-cases java-patterns-use-cases create_release_zip java/templates/java-gradle java-hello-world-gradle create_release_zip java/templates/java-maven java-hello-world-maven -create_release_zip java/templates/java-new-api-gradle java-hello-world-new-api-gradle -create_release_zip java/templates/java-new-api-maven java-hello-world-new-api-maven create_release_zip java/templates/java-maven-spring-boot java-hello-world-maven-spring-boot -create_release_zip java/templates/java-new-api-maven-spring-boot java-hello-world-new-api-maven-spring-boot create_release_zip java/templates/java-maven-quarkus java-hello-world-maven-quarkus create_release_zip java/tutorials/tour-of-orchestration-java java-tour-of-orchestration create_release_zip java/tutorials/tour-of-workflows-java java-tour-of-workflows diff --git a/.tools/run_jvm_tests.sh b/.tools/run_jvm_tests.sh index cb9b3e66..161454ee 100755 --- a/.tools/run_jvm_tests.sh +++ b/.tools/run_jvm_tests.sh @@ -7,11 +7,8 @@ PROJECT_ROOT="$(dirname "$SELF_PATH")/.." pushd $PROJECT_ROOT/java/templates/java-gradle && ./gradlew --console=plain check && popd pushd $PROJECT_ROOT/java/templates/java-maven && mvn -B verify && popd -pushd $PROJECT_ROOT/java/templates/java-new-api-gradle && ./gradlew --console=plain check && popd -pushd $PROJECT_ROOT/java/templates/java-new-api-maven && mvn -B verify && popd pushd $PROJECT_ROOT/java/templates/java-maven-quarkus && mvn -B verify && popd pushd $PROJECT_ROOT/java/templates/java-maven-spring-boot && mvn -B verify && popd -pushd $PROJECT_ROOT/java/templates/java-new-api-maven-spring-boot && mvn -B verify && popd pushd $PROJECT_ROOT/kotlin/templates/kotlin-gradle && ./gradlew --console=plain check && popd pushd $PROJECT_ROOT/kotlin/templates/kotlin-gradle-spring-boot && ./gradlew --console=plain check && popd diff --git a/.tools/update_jvm_examples.sh b/.tools/update_jvm_examples.sh index c63a72cf..d287c183 100755 --- a/.tools/update_jvm_examples.sh +++ b/.tools/update_jvm_examples.sh @@ -38,11 +38,8 @@ function search_and_replace_version_maven() { search_and_replace_version_gradle $PROJECT_ROOT/java/templates/java-gradle search_and_replace_version_maven $PROJECT_ROOT/java/templates/java-maven -search_and_replace_version_gradle $PROJECT_ROOT/java/templates/java-new-api-gradle -search_and_replace_version_maven $PROJECT_ROOT/java/templates/java-new-api-maven search_and_replace_version_maven $PROJECT_ROOT/java/templates/java-maven-quarkus search_and_replace_version_maven $PROJECT_ROOT/java/templates/java-maven-spring-boot -search_and_replace_version_maven $PROJECT_ROOT/java/templates/java-new-api-maven-spring-boot search_and_replace_version_gradle $PROJECT_ROOT/kotlin/templates/kotlin-gradle search_and_replace_version_gradle $PROJECT_ROOT/kotlin/templates/kotlin-gradle-spring-boot diff --git a/java/basics/build.gradle.kts b/java/basics/build.gradle.kts index c032bd24..4c77fe2d 100644 --- a/java/basics/build.gradle.kts +++ b/java/basics/build.gradle.kts @@ -7,11 +7,9 @@ repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-java-http:$restateVersion") @@ -27,7 +25,7 @@ dependencies { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -38,10 +36,18 @@ application { } else { mainClass.set("durable_execution.RoleUpdateService") } + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } tasks.withType { // Using -parameters allows to use Jackson ParameterName feature // https://github.com/FasterXML/jackson-modules-java8/tree/2.14/parameter-names options.compilerArgs.add("-parameters") +} + +tasks.named("test") { + useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } \ No newline at end of file diff --git a/java/basics/gradle/wrapper/gradle-wrapper.jar b/java/basics/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/java/basics/gradle/wrapper/gradle-wrapper.jar and b/java/basics/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/basics/gradle/wrapper/gradle-wrapper.properties b/java/basics/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/java/basics/gradle/wrapper/gradle-wrapper.properties +++ b/java/basics/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/basics/gradlew b/java/basics/gradlew index faf93008..249efbb0 100755 --- a/java/basics/gradlew +++ b/java/basics/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/java/basics/gradlew.bat b/java/basics/gradlew.bat index 9b42019c..8508ef68 100644 --- a/java/basics/gradlew.bat +++ b/java/basics/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/basics/src/main/java/building_blocks/MyService.java b/java/basics/src/main/java/building_blocks/MyService.java index 39673f6d..a663a349 100644 --- a/java/basics/src/main/java/building_blocks/MyService.java +++ b/java/basics/src/main/java/building_blocks/MyService.java @@ -1,11 +1,11 @@ package building_blocks; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.endpoint.Endpoint; import dev.restate.sdk.http.vertx.RestateHttpServer; -import virtual_objects.GreeterObjectClient; +import virtual_objects.GreeterObject; import java.time.Duration; import java.util.UUID; @@ -31,43 +31,43 @@ public class MyService { // This handler can be called over HTTP at http://restate:8080/MyService/handlerName // Use the context to access Restate's durable building blocks @Handler - public void run(Context ctx) throws Exception { + public void run() throws Exception { // 1. IDEMPOTENCY: Add an idempotency key to the header of your requests // Restate deduplicates calls automatically. Nothing to do here. // 2. DURABLE RPC: Call other services without manual retry and deduplication logic // Restate persists all requests and ensures execution till completion - String result = GreeterObjectClient.fromContext(ctx, "my-obj-key").greet("hello").await(); + String result = Restate.virtualObject(GreeterObject.class, "my-obj-key").greet("hello"); // 3. DURABLE MESSAGING: send (delayed) messages to other services without deploying a message broker // Restate persists the timers and triggers execution - GreeterObjectClient.fromContext(ctx, "my-obj-key").send().greet("hello"); + Restate.virtualObjectHandle(GreeterObject.class, "my-obj-key").send(GreeterObject::greet, "hello"); // 4. DURABLE PROMISES: tracked by Restate, can be moved between processes and survive failures // Awakeables: block the workflow until notified by another handler - var awakeable = ctx.awakeable(String.class); + var awakeable = Restate.awakeable(String.class); // Wait on the promise // If the process crashes while waiting, Restate will recover the promise somewhere else String greeting = awakeable.await(); // Another process can resolve the awakeable via its ID - ctx.awakeableHandle(awakeable.id()).resolve(String.class, "hello"); + Restate.awakeableHandle(awakeable.id()).resolve(String.class, "hello"); // 5. DURABLE TIMERS: sleep or wait for a timeout, tracked by Restate and recoverable // When this runs on FaaS, the handler suspends and the timer is tracked by Restate // Example of durable recoverable sleep // If the service crashes two seconds later, Restate will invoke it after another 3 seconds - ctx.sleep(Duration.ofSeconds(5)); + Restate.sleep(Duration.ofSeconds(5)); // Example of waiting on a promise (call/awakeable/...) or a timeout awakeable.await(Duration.ofSeconds(5000)); // Example of scheduling a handler for later on - GreeterObjectClient.fromContext(ctx, "my-obj-key").send().ungreet(Duration.ofDays(1)); + Restate.virtualObjectHandle(GreeterObject.class, "my-obj-key").send(GreeterObject::ungreet, Duration.ofDays(1)); // 7. PERSIST RESULTS: avoid re-execution of actions on retries // Use this for non-deterministic actions or interaction with APIs, DBs, ... // For example, generate idempotency keys that are stable across retries // Then use these to call other APIs and let them deduplicate String paymentDeduplicationID = UUID.randomUUID().toString(); - ctx.run(() -> chargeBankAccount(paymentDeduplicationID, 100)); + Restate.run("charge-bank-account", () -> chargeBankAccount(paymentDeduplicationID, 100)); } public static void main(String[] args) { diff --git a/java/basics/src/main/java/durable_execution/SubscriptionService.java b/java/basics/src/main/java/durable_execution/SubscriptionService.java index 869befa8..e0d7e06a 100644 --- a/java/basics/src/main/java/durable_execution/SubscriptionService.java +++ b/java/basics/src/main/java/durable_execution/SubscriptionService.java @@ -1,6 +1,6 @@ package durable_execution; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.endpoint.Endpoint; @@ -33,18 +33,18 @@ public class SubscriptionService { @Handler - public void add(Context ctx, SubscriptionRequest req) { - // Restate persists the result of all `ctx` actions and recovers them after failures + public void add(SubscriptionRequest req) { + // Restate persists the result of all `Restate` actions and recovers them after failures // For example, generate a stable idempotency key: - var paymentId = ctx.random().nextUUID().toString(); + var paymentId = Restate.random().nextUUID().toString(); - // ctx.run persists results of successful actions and skips execution on retries + // Restate.run persists results of successful actions and skips execution on retries // Failed actions (timeouts, API downtime, etc.) get retried - var payRef = ctx.run(String.class, () -> + var payRef = Restate.run("createRecurringPayment", String.class, () -> createRecurringPayment(req.creditCard(), paymentId)); for (String subscription : req.subscriptions()) { - ctx.run(() -> createSubscription(req.userId(), subscription, payRef)); + Restate.run("createSubscription", () -> createSubscription(req.userId(), subscription, payRef)); } } diff --git a/java/basics/src/main/java/virtual_objects/GreeterObject.java b/java/basics/src/main/java/virtual_objects/GreeterObject.java index db31bea4..6a087f6c 100644 --- a/java/basics/src/main/java/virtual_objects/GreeterObject.java +++ b/java/basics/src/main/java/virtual_objects/GreeterObject.java @@ -10,7 +10,7 @@ */ package virtual_objects; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.endpoint.Endpoint; @@ -38,26 +38,29 @@ public class GreeterObject { StateKey.of("count", Integer.TYPE); @Handler - public String greet(ObjectContext ctx, String greeting) { + public String greet(String greeting) { + var state = Restate.state(); // Access the state attached to this object (this 'name') // State access and updates are exclusive and consistent with the invocations - int count = ctx.get(COUNT).orElse(0); + int count = state.get(COUNT).orElse(0); int newCount = count + 1; - ctx.set(COUNT, newCount); + state.set(COUNT, newCount); - return String.format( "%s %s, for the %d-th time", greeting, ctx.key(), newCount); + return String.format( "%s %s, for the %d-th time", greeting, Restate.key(), newCount); } @Handler - public String ungreet(ObjectContext ctx) { - int count = ctx.get(COUNT).orElse(0); + public String ungreet() { + var state = Restate.state(); + + int count = state.get(COUNT).orElse(0); if(count > 0){ int newCount = count - 1; - ctx.set(COUNT, newCount); + state.set(COUNT, newCount); } - return String.format("Dear %s, taking one greeting back: %d", ctx.key(), count); + return String.format("Dear %s, taking one greeting back: %d", Restate.key(), count); } public static void main(String[] args) { diff --git a/java/basics/src/main/java/workflows/SignupWorkflow.java b/java/basics/src/main/java/workflows/SignupWorkflow.java index 2559ec56..6f78e4fa 100644 --- a/java/basics/src/main/java/workflows/SignupWorkflow.java +++ b/java/basics/src/main/java/workflows/SignupWorkflow.java @@ -10,8 +10,7 @@ */ package workflows; -import dev.restate.sdk.SharedWorkflowContext; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.endpoint.Endpoint; @@ -41,21 +40,21 @@ public class SignupWorkflow { // --- The workflow logic --- @Workflow - public boolean run(WorkflowContext ctx, User user) { + public boolean run(User user) { // workflow ID = user ID; workflow runs once per user - String userId = ctx.key(); + String userId = Restate.key(); // Durably executed action; write to other system - ctx.run(() -> createUserEntry(user)); + Restate.run("createUserEntry", () -> createUserEntry(user)); // Sent user email with verification link - String secret = ctx.random().nextUUID().toString(); - ctx.run(() -> sendEmailWithLink(userId, user, secret)); + String secret = Restate.random().nextUUID().toString(); + Restate.run("sendEmail", () -> sendEmailWithLink(userId, user, secret)); // Wait until user clicked email verification link // Promise gets resolved or rejected by the other handlers String clickSecret = - ctx.promise(LINK_CLICKED) + Restate.promise(LINK_CLICKED) .future() .await(); @@ -65,9 +64,9 @@ public boolean run(WorkflowContext ctx, User user) { // --- Other handlers interact with the workflow via queries and signals --- @Shared - public void click(SharedWorkflowContext ctx, String secret) { + public void click(String secret) { // Send data to the workflow via a durable promise - ctx.promiseHandle(LINK_CLICKED).resolve(secret); + Restate.promiseHandle(LINK_CLICKED).resolve(secret); } public static void main(String[] args) { diff --git a/java/end-to-end-applications/food-ordering/README.md b/java/end-to-end-applications/food-ordering/README.md index fe840a9f..83751698 100644 --- a/java/end-to-end-applications/food-ordering/README.md +++ b/java/end-to-end-applications/food-ordering/README.md @@ -86,7 +86,7 @@ The flow of an incoming order is as follows: 2. It then calls the `OrderStatusService` to create a new order in the system. The `OrderStatusService` is a virtual object which tracks the status of each order by storing it in Restate's key-value store. 3. The order workflow then triggers the payment by calling a third-party payment provider (implemented as a stub in this example). To do this, the order workflow first generates a stable idempotency token provided by the SDK, and then uses this to call the payment provider. The payment provider can deduplicate retries via the idempotency key. 4. The workflow then sets the order status to `SCHEDULED` and sets a timer to continue processing after the delivery delay has passed. For example, if a customer ordered food for later in the day, the order will be scheduled for preparation at the requested time. If any failures occur during the sleep, Restate makes sure that the workflow will still wake up on time. - 5. Once the timer fires, the order workflow creates an awakeable and sends a request to the restaurant point-of-sales system to start the preparation. This is done via an HTTP request wrapped in `ctx.run` to log the result in Restate. The status of the order is set to `IN_PREPARATION`. The restaurant will use the awakeable callback to signal when the prepration is done. Once this happens, the order workflow will continue and set the order status to `SCHEDULING_DELIVERY`. + 5. Once the timer fires, the order workflow creates an awakeable and sends a request to the restaurant point-of-sales system to start the preparation. This is done via an HTTP request wrapped in `Restate.run` to log the result in Restate. The status of the order is set to `IN_PREPARATION`. The restaurant will use the awakeable callback to signal when the prepration is done. Once this happens, the order workflow will continue and set the order status to `SCHEDULING_DELIVERY`. 6. Finally, the order workflow calls the delivery manager (`DeliveryManager.java`) to schedule the delivery of the order (see description below). It does this by using an awakeable, that the delivery manager will use to signal when the delivery is done. Once the delivery is done, the order workflow will set the order status to `DELIVERED`. ### The delivery workflow diff --git a/java/end-to-end-applications/food-ordering/app/build.gradle.kts b/java/end-to-end-applications/food-ordering/app/build.gradle.kts index 43d91a3f..73ee9c08 100644 --- a/java/end-to-end-applications/food-ordering/app/build.gradle.kts +++ b/java/end-to-end-applications/food-ordering/app/build.gradle.kts @@ -12,7 +12,7 @@ allprojects { java { // Configure the java toolchain to use. If not found, it will be downloaded automatically - toolchain { languageVersion = JavaLanguageVersion.of(17) } + toolchain { languageVersion = JavaLanguageVersion.of(25) } } } diff --git a/java/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.jar b/java/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.jar index 1b33c55b..b1b8ef56 100644 Binary files a/java/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.jar and b/java/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.properties b/java/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/java/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.properties +++ b/java/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/end-to-end-applications/food-ordering/app/gradlew b/java/end-to-end-applications/food-ordering/app/gradlew index 23d15a93..249efbb0 100755 --- a/java/end-to-end-applications/food-ordering/app/gradlew +++ b/java/end-to-end-applications/food-ordering/app/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/java/end-to-end-applications/food-ordering/app/gradlew.bat b/java/end-to-end-applications/food-ordering/app/gradlew.bat index 5eed7ee8..8508ef68 100644 --- a/java/end-to-end-applications/food-ordering/app/gradlew.bat +++ b/java/end-to-end-applications/food-ordering/app/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH= -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/build.gradle.kts b/java/end-to-end-applications/food-ordering/app/restate-app/build.gradle.kts index 8efad182..5c9fcb88 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/build.gradle.kts +++ b/java/end-to-end-applications/food-ordering/app/restate-app/build.gradle.kts @@ -3,20 +3,18 @@ import java.net.URI plugins { java application - id ("com.google.cloud.tools.jib") version "3.4.0" - id("com.diffplug.spotless") version "6.25.0" + id ("com.google.cloud.tools.jib") version "3.5.3" + id("com.diffplug.spotless") version "8.8.0" } repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { // Restate SDK - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") - implementation("dev.restate:sdk-java-http:$restateVersion") // Jackson parameter names @@ -36,6 +34,14 @@ dependencies { // Set main class application { mainClass.set("dev.restate.sdk.examples.AppMain") + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} + +tasks.named("test") { + useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } jib { diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/AppMain.java b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/AppMain.java index f40f96ef..62ed1eca 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/AppMain.java +++ b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/AppMain.java @@ -11,18 +11,16 @@ package dev.restate.sdk.examples; - import dev.restate.sdk.endpoint.Endpoint; import dev.restate.sdk.http.vertx.RestateHttpServer; public class AppMain { public static void main(String[] args) { RestateHttpServer.listen( - Endpoint.bind(new OrderWorkflow()) - .bind(new OrderStatusService()) - .bind(new DeliveryManager()) - .bind(new DriverDeliveryMatcher()) - .bind(new DriverDigitalTwin()) - ); + Endpoint.bind(new OrderWorkflow()) + .bind(new OrderStatusService()) + .bind(new DeliveryManager()) + .bind(new DriverDeliveryMatcher()) + .bind(new DriverDigitalTwin())); } } diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DeliveryManager.java b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DeliveryManager.java index 690eb81e..58ef0680 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DeliveryManager.java +++ b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DeliveryManager.java @@ -11,13 +11,13 @@ package dev.restate.sdk.examples; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.examples.types.*; -import dev.restate.sdk.examples.utils.GeoUtils; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.common.TerminalException; +import dev.restate.sdk.examples.types.*; +import dev.restate.sdk.examples.utils.GeoUtils; /** * Manages the delivery of the order to the customer. Keyed by the order ID (similar to the @@ -35,12 +35,14 @@ public class DeliveryManager { * Gets called by the OrderService when a new order has been prepared and needs to be delivered. */ @Handler - public void start(ObjectContext ctx, DeliveryRequest request) throws TerminalException { - String orderId = ctx.key(); + public void start(DeliveryRequest request) throws TerminalException { + String orderId = Restate.key(); // Temporary placeholder: random location - var restaurantLocation = ctx.run(Location.class, GeoUtils::randomLocation); - var customerLocation = ctx.run(Location.class, GeoUtils::randomLocation); + var restaurantLocation = + Restate.run("restaurant location", Location.class, GeoUtils::randomLocation); + var customerLocation = + Restate.run("customer location", Location.class, GeoUtils::randomLocation); // Store the delivery information in Restate's state store DeliveryInformation deliveryInfo = @@ -51,29 +53,26 @@ public void start(ObjectContext ctx, DeliveryRequest request) throws TerminalExc restaurantLocation, customerLocation, false); - ctx.set(DELIVERY_INFO, deliveryInfo); + Restate.state().set(DELIVERY_INFO, deliveryInfo); // Acquire a driver - var driverAwakeable = ctx.awakeable(String.class); - DriverDeliveryMatcherClient.fromContext(ctx, GeoUtils.DEMO_REGION) - .send() - .requestDriverForDelivery(driverAwakeable.id()); + var driverAwakeable = Restate.awakeable(String.class); + Restate.virtualObjectHandle(DriverDeliveryMatcher.class, GeoUtils.DEMO_REGION) + .send(DriverDeliveryMatcher::requestDriverForDelivery, driverAwakeable.id()); // Wait until the driver pool service has located a driver // This awakeable gets resolved either immediately when there is a pending delivery // or later, when a new delivery comes in. var driverId = driverAwakeable.await(); // Assign the driver to the job - DriverDigitalTwinClient.fromContext(ctx, driverId) + Restate.virtualObject(DriverDigitalTwin.class, driverId) .assignDeliveryJob( new AssignDeliveryRequest( - orderId, request.getRestaurantId(), restaurantLocation, customerLocation)) - .await(); + orderId, request.getRestaurantId(), restaurantLocation, customerLocation)); // Update the status of the order to "waiting for the driver" - OrderStatusServiceClient.fromContext(ctx, orderId) - .send() - .setStatus(StatusEnum.WAITING_FOR_DRIVER); + Restate.virtualObjectHandle(OrderStatusService.class, orderId) + .send(OrderStatusService::setStatus, StatusEnum.WAITING_FOR_DRIVER); } /** @@ -81,20 +80,23 @@ public void start(ObjectContext ctx, DeliveryRequest request) throws TerminalExc * when the driver has arrived at the restaurant. */ @Handler - public void notifyDeliveryPickup(ObjectContext ctx) throws TerminalException { + public void notifyDeliveryPickup() throws TerminalException { + var state = Restate.state(); // Retrieve the delivery information for this delivery var delivery = - ctx.get(DELIVERY_INFO) + state + .get(DELIVERY_INFO) .orElseThrow( () -> new TerminalException( "Delivery was picked up but there is no ongoing delivery.")); // Update the status of the delivery to "picked up" delivery.notifyPickup(); - ctx.set(DELIVERY_INFO, delivery); + state.set(DELIVERY_INFO, delivery); // Update the status of the order to "in delivery" - OrderStatusServiceClient.fromContext(ctx, ctx.key()).send().setStatus(StatusEnum.IN_DELIVERY); + Restate.virtualObjectHandle(OrderStatusService.class, Restate.key()) + .send(OrderStatusService::setStatus, StatusEnum.IN_DELIVERY); } /** @@ -102,19 +104,21 @@ public void notifyDeliveryPickup(ObjectContext ctx) throws TerminalException { * when the driver has delivered the order to the customer. */ @Handler - public void notifyDeliveryDelivered(ObjectContext ctx) throws TerminalException { + public void notifyDeliveryDelivered() throws TerminalException { + var state = Restate.state(); // Retrieve the delivery information for this delivery var delivery = - ctx.get(DELIVERY_INFO) + state + .get(DELIVERY_INFO) .orElseThrow( () -> new TerminalException( "Delivery was delivered but there is no ongoing delivery.")); // Order has been delivered, so state can be cleared - ctx.clear(DELIVERY_INFO); + state.clear(DELIVERY_INFO); // Notify the OrderService that the delivery has been completed - ctx.awakeableHandle(delivery.getCallbackId()).resolve(Void.class, null); + Restate.awakeableHandle(delivery.getCallbackId()).resolve(Void.class, null); } /** @@ -123,11 +127,11 @@ public void notifyDeliveryDelivered(ObjectContext ctx) throws TerminalException * has moved to a new location. */ @Handler - public void handleDriverLocationUpdate(ObjectContext ctx, Location newLocation) - throws TerminalException { + public void handleDriverLocationUpdate(Location newLocation) throws TerminalException { // Retrieve the delivery information for this delivery var delivery = - ctx.get(DELIVERY_INFO) + Restate.state() + .get(DELIVERY_INFO) .orElseThrow( () -> new TerminalException( @@ -142,6 +146,7 @@ public void handleDriverLocationUpdate(ObjectContext ctx, Location newLocation) delivery.getRestaurantLocation(), delivery.getCustomerLocation()); // Update the ETA of the order - OrderStatusServiceClient.fromContext(ctx, ctx.key()).send().setETA(eta); + Restate.virtualObjectHandle(OrderStatusService.class, Restate.key()) + .send(OrderStatusService::setETA, eta); } } diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DriverDeliveryMatcher.java b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DriverDeliveryMatcher.java index 42cee583..b9bae3b0 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DriverDeliveryMatcher.java +++ b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DriverDeliveryMatcher.java @@ -11,14 +11,12 @@ package dev.restate.sdk.examples; -import com.fasterxml.jackson.core.type.TypeReference; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.common.TerminalException; import dev.restate.serde.TypeRef; - import java.util.LinkedList; import java.util.Queue; @@ -43,23 +41,24 @@ public class DriverDeliveryMatcher { * in line. If no pending deliveries, driver is added to the available driver pool */ @Handler - public void setDriverAvailable(ObjectContext ctx, String driverId) throws TerminalException { - var pendingDeliveries = ctx.get(PENDING_DELIVERIES).orElse(new LinkedList<>()); + public void setDriverAvailable(String driverId) throws TerminalException { + var state = Restate.state(); + var pendingDeliveries = state.get(PENDING_DELIVERIES).orElse(new LinkedList<>()); // If there is a pending delivery, assign it to the driver var nextDelivery = pendingDeliveries.poll(); if (nextDelivery != null) { // Update the queue in state. Delivery was removed. - ctx.set(PENDING_DELIVERIES, pendingDeliveries); + state.set(PENDING_DELIVERIES, pendingDeliveries); // Notify that delivery is ongoing - ctx.awakeableHandle(nextDelivery).resolve(String.class, driverId); + Restate.awakeableHandle(nextDelivery).resolve(String.class, driverId); return; } // Otherwise remember driver as available - var availableDrivers = ctx.get(AVAILABLE_DRIVERS).orElse(new LinkedList<>()); + var availableDrivers = state.get(AVAILABLE_DRIVERS).orElse(new LinkedList<>()); availableDrivers.offer(driverId); - ctx.set(AVAILABLE_DRIVERS, availableDrivers); + state.set(AVAILABLE_DRIVERS, availableDrivers); } /** @@ -67,23 +66,23 @@ public void setDriverAvailable(ObjectContext ctx, String driverId) throws Termin * available. If no available drivers, the delivery is added to the pending deliveries queue */ @Handler - public void requestDriverForDelivery(ObjectContext ctx, String deliveryCallbackId) - throws TerminalException { - var availableDrivers = ctx.get(AVAILABLE_DRIVERS).orElse(new LinkedList<>()); + public void requestDriverForDelivery(String deliveryCallbackId) throws TerminalException { + var state = Restate.state(); + var availableDrivers = state.get(AVAILABLE_DRIVERS).orElse(new LinkedList<>()); // If a driver is available, assign the delivery right away var nextAvailableDriver = availableDrivers.poll(); if (nextAvailableDriver != null) { // Remove driver from the pool - ctx.set(AVAILABLE_DRIVERS, availableDrivers); + state.set(AVAILABLE_DRIVERS, availableDrivers); // Notify that delivery is ongoing - ctx.awakeableHandle(deliveryCallbackId).resolve(String.class, nextAvailableDriver); + Restate.awakeableHandle(deliveryCallbackId).resolve(String.class, nextAvailableDriver); return; } // otherwise store the delivery request until a new driver becomes available - var pendingDeliveries = ctx.get(PENDING_DELIVERIES).orElse(new LinkedList<>()); + var pendingDeliveries = state.get(PENDING_DELIVERIES).orElse(new LinkedList<>()); pendingDeliveries.offer(deliveryCallbackId); - ctx.set(PENDING_DELIVERIES, pendingDeliveries); + state.set(PENDING_DELIVERIES, pendingDeliveries); } } diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DriverDigitalTwin.java b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DriverDigitalTwin.java index b9818402..ecd5a315 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DriverDigitalTwin.java +++ b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/DriverDigitalTwin.java @@ -11,16 +11,15 @@ package dev.restate.sdk.examples; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; +import dev.restate.sdk.common.StateKey; +import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.examples.types.AssignDeliveryRequest; import dev.restate.sdk.examples.types.AssignedDelivery; import dev.restate.sdk.examples.types.DriverStatus; import dev.restate.sdk.examples.types.Location; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.TerminalException; - import java.util.Optional; /** @@ -48,11 +47,12 @@ public class DriverDigitalTwin { * (DriverMobileAppSimulator) calls this method. */ @Handler - public void setDriverAvailable(ObjectContext ctx, String region) throws TerminalException { - expectStatus(ctx, DriverStatus.IDLE); + public void setDriverAvailable(String region) throws TerminalException { + expectStatus(DriverStatus.IDLE); - ctx.set(DRIVER_STATUS, DriverStatus.WAITING_FOR_WORK); - DriverDeliveryMatcherClient.fromContext(ctx, region).send().setDriverAvailable(ctx.key()); + Restate.state().set(DRIVER_STATUS, DriverStatus.WAITING_FOR_WORK); + Restate.virtualObjectHandle(DriverDeliveryMatcher.class, region) + .send(DriverDeliveryMatcher::setDriverAvailable, Restate.key()); } /** @@ -61,89 +61,90 @@ public void setDriverAvailable(ObjectContext ctx, String region) throws Terminal * location. */ @Handler - public void assignDeliveryJob(ObjectContext ctx, AssignDeliveryRequest request) - throws TerminalException { - expectStatus(ctx, DriverStatus.WAITING_FOR_WORK); + public void assignDeliveryJob(AssignDeliveryRequest request) throws TerminalException { + expectStatus(DriverStatus.WAITING_FOR_WORK); + var state = Restate.state(); // Update the status and assigned delivery information of the driver - ctx.set(DRIVER_STATUS, DriverStatus.DELIVERING); - ctx.set( + state.set(DRIVER_STATUS, DriverStatus.DELIVERING); + state.set( ASSIGNED_DELIVERY, new AssignedDelivery( - ctx.key(), + Restate.key(), request.getOrderId(), request.getRestaurantId(), request.getRestaurantLocation(), request.getCustomerLocation())); // Notify current location to the delivery service - ctx.get(DRIVER_LOCATION) + state + .get(DRIVER_LOCATION) .ifPresent( loc -> - DeliveryManagerClient.fromContext(ctx, request.getOrderId()) - .send() - .handleDriverLocationUpdate(loc)); + Restate.virtualObjectHandle(DeliveryManager.class, request.getOrderId()) + .send(DeliveryManager::handleDriverLocationUpdate, loc)); } /** * Gets called by the driver's mobile app when he has picked up the delivery from the restaurant. */ @Handler - public void notifyDeliveryPickup(ObjectContext ctx) throws TerminalException { - expectStatus(ctx, DriverStatus.DELIVERING); + public void notifyDeliveryPickup() throws TerminalException { + expectStatus(DriverStatus.DELIVERING); + var state = Restate.state(); // Retrieve the ongoing delivery and update its status var currentDelivery = - ctx.get(ASSIGNED_DELIVERY) + state + .get(ASSIGNED_DELIVERY) .orElseThrow( () -> new TerminalException( "Driver is in status DELIVERING but there is no current delivery set.")); currentDelivery.notifyPickup(); - ctx.set(ASSIGNED_DELIVERY, currentDelivery); + state.set(ASSIGNED_DELIVERY, currentDelivery); // Update the status of the delivery in the delivery manager - DeliveryManagerClient.fromContext(ctx, currentDelivery.getOrderId()) - .send() - .notifyDeliveryPickup(); + Restate.virtualObjectHandle(DeliveryManager.class, currentDelivery.getOrderId()) + .send(DeliveryManager::notifyDeliveryPickup); } /** Gets called by the driver's mobile app when he has delivered the order to the customer. */ @Handler - public void notifyDeliveryDelivered(ObjectContext ctx) throws TerminalException { - expectStatus(ctx, DriverStatus.DELIVERING); + public void notifyDeliveryDelivered() throws TerminalException { + expectStatus(DriverStatus.DELIVERING); + var state = Restate.state(); // Retrieve the ongoing delivery var assignedDelivery = - ctx.get(ASSIGNED_DELIVERY) + state + .get(ASSIGNED_DELIVERY) .orElseThrow( () -> new TerminalException( "Driver is in status DELIVERING but there is no current delivery set.")); // Clean up the state - ctx.clear(ASSIGNED_DELIVERY); + state.clear(ASSIGNED_DELIVERY); // Notify the delivery service that the delivery was delivered - DeliveryManagerClient.fromContext(ctx, assignedDelivery.getOrderId()) - .send() - .notifyDeliveryDelivered(); + Restate.virtualObjectHandle(DeliveryManager.class, assignedDelivery.getOrderId()) + .send(DeliveryManager::notifyDeliveryDelivered); // Update the status of the driver to idle - ctx.set(DRIVER_STATUS, DriverStatus.IDLE); + state.set(DRIVER_STATUS, DriverStatus.IDLE); } /** Gets called by the driver's mobile app when he has moved to a new location. */ @Handler - public void handleDriverLocationUpdateEvent(ObjectContext ctx, Location location) { + public void handleDriverLocationUpdateEvent(Location location) { // Update the location of the driver - ctx.set(DRIVER_LOCATION, location); + Restate.state().set(DRIVER_LOCATION, location); // Update the location of the delivery, if there is one - Optional assignedDelivery = ctx.get(ASSIGNED_DELIVERY); + Optional assignedDelivery = Restate.state().get(ASSIGNED_DELIVERY); if (assignedDelivery.isPresent()) { - DeliveryManagerClient.fromContext(ctx, assignedDelivery.get().getOrderId()) - .send() - .handleDriverLocationUpdate(location); + Restate.virtualObjectHandle(DeliveryManager.class, assignedDelivery.get().getOrderId()) + .send(DeliveryManager::handleDriverLocationUpdate, location); } } @@ -153,16 +154,16 @@ public void handleDriverLocationUpdateEvent(ObjectContext ctx, Location location * got assigned to him. */ @Handler - public Optional getAssignedDelivery(ObjectContext ctx) { - return ctx.get(ASSIGNED_DELIVERY); + public Optional getAssignedDelivery() { + return Restate.state().get(ASSIGNED_DELIVERY); } // Utility function to check if the driver is in the expected state // If the driver is in a different state, a terminal exception is thrown that stops any retries // from taking place. // Is only called from inside the driver service. - private void expectStatus(ObjectContext ctx, DriverStatus expectedStatus) { - var currentStatus = ctx.get(DRIVER_STATUS).orElse(DriverStatus.IDLE); + private void expectStatus(DriverStatus expectedStatus) { + var currentStatus = Restate.state().get(DRIVER_STATUS).orElse(DriverStatus.IDLE); if (currentStatus != expectedStatus) { throw new TerminalException( diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/OrderStatusService.java b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/OrderStatusService.java index 804fe18e..9f73f5eb 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/OrderStatusService.java +++ b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/OrderStatusService.java @@ -11,12 +11,12 @@ package dev.restate.sdk.examples; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.examples.types.StatusEnum; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.common.TerminalException; +import dev.restate.sdk.examples.types.StatusEnum; @VirtualObject public class OrderStatusService { @@ -45,19 +45,20 @@ public StatusEnum getStatus() { /** Gets called by the webUI frontend to display the status of an order. */ @Handler - public OrderStatus get(ObjectContext ctx) throws TerminalException { - var status = ctx.get(ORDER_STATUS).orElse(StatusEnum.NEW); - var eta = ctx.get(ORDER_ETA).orElse(-1L); + public OrderStatus get() throws TerminalException { + var state = Restate.state(); + var status = state.get(ORDER_STATUS).orElse(StatusEnum.NEW); + var eta = state.get(ORDER_ETA).orElse(-1L); return new OrderStatus(status, eta); } @Handler - public void setStatus(ObjectContext ctx, StatusEnum statusEnum) throws TerminalException { - ctx.set(ORDER_STATUS, statusEnum); + public void setStatus(StatusEnum statusEnum) throws TerminalException { + Restate.state().set(ORDER_STATUS, statusEnum); } @Handler - public void setETA(ObjectContext ctx, long eta) throws TerminalException { - ctx.set(ORDER_ETA, eta); + public void setETA(long eta) throws TerminalException { + Restate.state().set(ORDER_ETA, eta); } } diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/OrderWorkflow.java b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/OrderWorkflow.java index 5049b997..0cfe61b5 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/OrderWorkflow.java +++ b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/OrderWorkflow.java @@ -11,16 +11,15 @@ package dev.restate.sdk.examples; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; +import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.examples.clients.PaymentClient; import dev.restate.sdk.examples.clients.RestaurantClient; import dev.restate.sdk.examples.types.DeliveryRequest; import dev.restate.sdk.examples.types.OrderRequest; import dev.restate.sdk.examples.types.StatusEnum; -import dev.restate.sdk.common.TerminalException; - import java.time.Duration; /** @@ -34,18 +33,18 @@ public class OrderWorkflow { private final PaymentClient paymentClnt = PaymentClient.get(); @Handler - public void create(Context ctx, OrderRequest order) throws TerminalException { + public void create(OrderRequest order) throws TerminalException { String id = order.getOrderId(); - var orderStatusService = OrderStatusServiceClient.fromContext(ctx, id); + var orderStatusService = Restate.virtualObject(OrderStatusService.class, id); // 1. Set status orderStatusService.setStatus(StatusEnum.CREATED); // 2. Handle payment - String token = ctx.random().nextUUID().toString(); + String token = Restate.random().nextUUID().toString(); boolean paid = - ctx.run( + Restate.run( "process payment", Boolean.TYPE, () -> paymentClnt.charge(id, token, order.getTotalCost())); @@ -58,22 +57,23 @@ public void create(Context ctx, OrderRequest order) throws TerminalException { // 3. Schedule preparation orderStatusService.setStatus(StatusEnum.SCHEDULED); - ctx.sleep(Duration.ofMillis(order.getDeliveryDelay())); + Restate.sleep(Duration.ofMillis(order.getDeliveryDelay())); // 4. Trigger preparation - var preparationFuture = ctx.awakeable(Void.class); - ctx.run("notify restaurant", () -> restaurant.prepare(id, preparationFuture.id())); + var preparationFuture = Restate.awakeable(Void.class); + Restate.run("notify restaurant", () -> restaurant.prepare(id, preparationFuture.id())); orderStatusService.setStatus(StatusEnum.IN_PREPARATION); preparationFuture.await(); orderStatusService.setStatus(StatusEnum.SCHEDULING_DELIVERY); // 5. Find a driver and start delivery - var deliveryAwakeable = ctx.awakeable(Void.class); + var deliveryAwakeable = Restate.awakeable(Void.class); - DeliveryManagerClient.fromContext(ctx, id) - .send() - .start(new DeliveryRequest(order.getRestaurantId(), deliveryAwakeable.id())); + Restate.virtualObjectHandle(DeliveryManager.class, id) + .send( + DeliveryManager::start, + new DeliveryRequest(order.getRestaurantId(), deliveryAwakeable.id())); deliveryAwakeable.await(); orderStatusService.setStatus(StatusEnum.DELIVERED); } diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/clients/RestaurantClient.java b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/clients/RestaurantClient.java index 1200eec8..74913b64 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/clients/RestaurantClient.java +++ b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/clients/RestaurantClient.java @@ -12,7 +12,6 @@ package dev.restate.sdk.examples.clients; import dev.restate.sdk.common.TerminalException; - import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; diff --git a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/external/DriverMobileAppSimulator.java b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/external/DriverMobileAppSimulator.java index 6b32ff4d..a542c267 100644 --- a/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/external/DriverMobileAppSimulator.java +++ b/java/end-to-end-applications/food-ordering/app/restate-app/src/main/java/dev/restate/sdk/examples/external/DriverMobileAppSimulator.java @@ -11,20 +11,20 @@ package dev.restate.sdk.examples.external; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; +import dev.restate.sdk.common.StateKey; +import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.endpoint.Endpoint; import dev.restate.sdk.examples.*; import dev.restate.sdk.examples.clients.KafkaPublisher; import dev.restate.sdk.examples.types.AssignedDelivery; import dev.restate.sdk.examples.types.Location; import dev.restate.sdk.examples.utils.GeoUtils; -import java.time.Duration; import dev.restate.sdk.http.vertx.RestateHttpServer; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.TerminalException; import dev.restate.serde.jackson.JacksonSerdes; +import java.time.Duration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -54,24 +54,26 @@ public class DriverMobileAppSimulator { /** Mimics the driver setting himself to available in the app */ @Handler - public void startDriver(ObjectContext ctx) throws TerminalException { + public void startDriver() throws TerminalException { + var state = Restate.state(); // If this driver was already created, do nothing - if (ctx.get(CURRENT_LOCATION).isPresent()) { + if (state.get(CURRENT_LOCATION).isPresent()) { return; } - logger.info("Starting driver " + ctx.key()); - var location = ctx.run(Location.class, GeoUtils::randomLocation); - ctx.set(CURRENT_LOCATION, location); - producer.sendDriverUpdate(ctx.key(), JacksonSerdes.of(Location.class).serialize(location).toByteArray()); + logger.info("Starting driver " + Restate.key()); + var location = Restate.run("current location", Location.class, GeoUtils::randomLocation); + state.set(CURRENT_LOCATION, location); + producer.sendDriverUpdate( + Restate.key(), JacksonSerdes.of(Location.class).serialize(location).toByteArray()); // Tell the digital twin of the driver in the food ordering app, that he is available - DriverDigitalTwinClient.fromContext(ctx, ctx.key()) - .setDriverAvailable(GeoUtils.DEMO_REGION) - .await(); + Restate.virtualObject(DriverDigitalTwin.class, Restate.key()) + .setDriverAvailable(GeoUtils.DEMO_REGION); // Start polling for work - DriverMobileAppSimulatorClient.fromContext(ctx, ctx.key()).send().pollForWork(); + Restate.virtualObjectHandle(DriverMobileAppSimulator.class, Restate.key()) + .send(DriverMobileAppSimulator::pollForWork); } /** @@ -79,16 +81,16 @@ public void startDriver(ObjectContext ctx) throws TerminalException { * again after a short delay. */ @Handler - public void pollForWork(ObjectContext ctx) throws TerminalException { - var thisDriverSim = DriverMobileAppSimulatorClient.fromContext(ctx, ctx.key()); + public void pollForWork() throws TerminalException { + var thisDriverSim = Restate.virtualObjectHandle(DriverMobileAppSimulator.class, Restate.key()); // Ask the digital twin of the driver in the food ordering app, if he already got a job assigned var optionalAssignedDelivery = - DriverDigitalTwinClient.fromContext(ctx, ctx.key()).getAssignedDelivery().await(); + Restate.virtualObject(DriverDigitalTwin.class, Restate.key()).getAssignedDelivery(); // If there is no job, ask again after a short delay if (optionalAssignedDelivery.isEmpty()) { - thisDriverSim.send().pollForWork(Duration.ofMillis(POLL_INTERVAL)); + thisDriverSim.send(DriverMobileAppSimulator::pollForWork, Duration.ofMillis(POLL_INTERVAL)); return; } @@ -101,21 +103,24 @@ public void pollForWork(ObjectContext ctx) throws TerminalException { delivery.getRestaurantId(), delivery.getRestaurantLocation(), delivery.getCustomerLocation()); - ctx.set(ASSIGNED_DELIVERY, newAssignedDelivery); + Restate.state().set(ASSIGNED_DELIVERY, newAssignedDelivery); // Start moving to the delivery pickup location - thisDriverSim.send().move(Duration.ofMillis(MOVE_INTERVAL)); + thisDriverSim.send(DriverMobileAppSimulator::move, Duration.ofMillis(MOVE_INTERVAL)); } /** Periodically lets the food ordering app know the new location */ @Handler - public void move(ObjectContext ctx) throws TerminalException { - var thisDriverSim = DriverMobileAppSimulatorClient.fromContext(ctx, ctx.key()); + public void move() throws TerminalException { + var state = Restate.state(); + var thisDriverSim = Restate.virtualObjectHandle(DriverMobileAppSimulator.class, Restate.key()); var assignedDelivery = - ctx.get(ASSIGNED_DELIVERY) + state + .get(ASSIGNED_DELIVERY) .orElseThrow(() -> new TerminalException("Driver has no delivery assigned")); var currentLocation = - ctx.get(CURRENT_LOCATION) + state + .get(CURRENT_LOCATION) .orElseThrow(() -> new TerminalException("Driver has no location assigned")); // Get next destination to go to @@ -126,29 +131,30 @@ public void move(ObjectContext ctx) throws TerminalException { // Move to the next location var newLocation = GeoUtils.moveToDestination(currentLocation, nextDestination); - ctx.set(CURRENT_LOCATION, newLocation); - producer.sendDriverUpdate(ctx.key(), JacksonSerdes.of(Location.class).serialize(newLocation).toByteArray()); + state.set(CURRENT_LOCATION, newLocation); + producer.sendDriverUpdate( + Restate.key(), JacksonSerdes.of(Location.class).serialize(newLocation).toByteArray()); // If we reached the destination, notify the food ordering app if (newLocation.equals(nextDestination)) { // If the delivery was already picked up, then that means it now arrived at the customer if (assignedDelivery.isOrderPickedUp()) { // Delivery is delivered to customer - ctx.clear(ASSIGNED_DELIVERY); + state.clear(ASSIGNED_DELIVERY); // Notify the driver's digital twin in the food ordering app of the delivery success - DriverDigitalTwinClient.fromContext(ctx, ctx.key()).notifyDeliveryDelivered().await(); + Restate.virtualObject(DriverDigitalTwin.class, Restate.key()).notifyDeliveryDelivered(); // Take a small break before starting the next delivery - ctx.sleep(Duration.ofMillis(PAUSE_BETWEEN_DELIVERIES)); + Restate.sleep(Duration.ofMillis(PAUSE_BETWEEN_DELIVERIES)); // Tell the driver's digital twin in the food ordering app, that he is available - DriverDigitalTwinClient.fromContext(ctx, ctx.key()) - .send() - .setDriverAvailable(GeoUtils.DEMO_REGION); + Restate.virtualObjectHandle(DriverDigitalTwin.class, Restate.key()) + .send(DriverDigitalTwin::setDriverAvailable, GeoUtils.DEMO_REGION); // Start polling for work - DriverMobileAppSimulatorClient.fromContext(ctx, ctx.key()).send().pollForWork(); + Restate.virtualObjectHandle(DriverMobileAppSimulator.class, Restate.key()) + .send(DriverMobileAppSimulator::pollForWork); return; } @@ -156,12 +162,12 @@ public void move(ObjectContext ctx) throws TerminalException { // restaurant // and will start the delivery assignedDelivery.notifyPickup(); - ctx.set(ASSIGNED_DELIVERY, assignedDelivery); - DriverDigitalTwinClient.fromContext(ctx, ctx.key()).notifyDeliveryPickup().await(); + state.set(ASSIGNED_DELIVERY, assignedDelivery); + Restate.virtualObject(DriverDigitalTwin.class, Restate.key()).notifyDeliveryPickup(); } // Call this method again after a short delay - thisDriverSim.send().move(Duration.ofMillis(MOVE_INTERVAL)); + thisDriverSim.send(DriverMobileAppSimulator::move, Duration.ofMillis(MOVE_INTERVAL)); } public static void main(String[] args) { diff --git a/java/end-to-end-applications/food-ordering/app/restaurant/build.gradle.kts b/java/end-to-end-applications/food-ordering/app/restaurant/build.gradle.kts index 8696d576..d9ecc494 100644 --- a/java/end-to-end-applications/food-ordering/app/restaurant/build.gradle.kts +++ b/java/end-to-end-applications/food-ordering/app/restaurant/build.gradle.kts @@ -3,14 +3,14 @@ import java.net.URI plugins { java application - id ("com.google.cloud.tools.jib") version "3.4.0" + id ("com.google.cloud.tools.jib") version "3.5.3" } repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { // Kafka @@ -28,6 +28,14 @@ dependencies { // Set main class application { mainClass.set("dev.restate.sdk.examples.RestaurantMain") + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} + +tasks.named("test") { + useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } diff --git a/java/end-to-end-applications/subway-fare-calculator/build.gradle.kts b/java/end-to-end-applications/subway-fare-calculator/build.gradle.kts index 313c460c..76c9a9e5 100644 --- a/java/end-to-end-applications/subway-fare-calculator/build.gradle.kts +++ b/java/end-to-end-applications/subway-fare-calculator/build.gradle.kts @@ -7,20 +7,27 @@ repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") implementation("dev.restate:sdk-java-http:$restateVersion") } // Set main class application { mainClass.set("dev.restate.example.CardTracker") + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } java { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(25)) } -} \ No newline at end of file +} + +tasks.named("test") { + useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} diff --git a/java/end-to-end-applications/subway-fare-calculator/gradle/wrapper/gradle-wrapper.jar b/java/end-to-end-applications/subway-fare-calculator/gradle/wrapper/gradle-wrapper.jar index 1b33c55b..b1b8ef56 100644 Binary files a/java/end-to-end-applications/subway-fare-calculator/gradle/wrapper/gradle-wrapper.jar and b/java/end-to-end-applications/subway-fare-calculator/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/end-to-end-applications/subway-fare-calculator/gradle/wrapper/gradle-wrapper.properties b/java/end-to-end-applications/subway-fare-calculator/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/java/end-to-end-applications/subway-fare-calculator/gradle/wrapper/gradle-wrapper.properties +++ b/java/end-to-end-applications/subway-fare-calculator/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/end-to-end-applications/subway-fare-calculator/gradlew b/java/end-to-end-applications/subway-fare-calculator/gradlew index 23d15a93..249efbb0 100755 --- a/java/end-to-end-applications/subway-fare-calculator/gradlew +++ b/java/end-to-end-applications/subway-fare-calculator/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/java/end-to-end-applications/subway-fare-calculator/gradlew.bat b/java/end-to-end-applications/subway-fare-calculator/gradlew.bat index 5eed7ee8..8508ef68 100644 --- a/java/end-to-end-applications/subway-fare-calculator/gradlew.bat +++ b/java/end-to-end-applications/subway-fare-calculator/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH= -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/end-to-end-applications/subway-fare-calculator/src/main/java/dev/restate/example/CardTracker.java b/java/end-to-end-applications/subway-fare-calculator/src/main/java/dev/restate/example/CardTracker.java index 937be773..de310c3b 100644 --- a/java/end-to-end-applications/subway-fare-calculator/src/main/java/dev/restate/example/CardTracker.java +++ b/java/end-to-end-applications/subway-fare-calculator/src/main/java/dev/restate/example/CardTracker.java @@ -1,8 +1,7 @@ package dev.restate.example; import dev.restate.example.apis.CardStatusServiceApi; -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.SharedObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.VirtualObject; @@ -23,17 +22,18 @@ public class CardTracker { private static final StateKey TOTAL_FARE_TODAY = StateKey.of("fare", DailyFare.class); @Handler - public String badgeIn(ObjectContext ctx, String station) { - final String cardId = ctx.key(); + public String badgeIn(String station) { + final String cardId = Restate.key(); + var state = Restate.state(); // authorize card if we haven't done that today - final boolean authorized = ctx.get(AUTHORIZED).orElseGet(() -> { - boolean success = Payments.authorizeCard(ctx, cardId); - ctx.set(AUTHORIZED, success); + final boolean authorized = state.get(AUTHORIZED).orElseGet(() -> { + boolean success = Payments.authorizeCard(cardId); + state.set(AUTHORIZED, success); if (!success) { // make call to status cache to block this card at the gates - ctx.run("block card", () -> CardStatusServiceApi.tagCardAsBlocked(cardId)); + Restate.run("block card", () -> CardStatusServiceApi.tagCardAsBlocked(cardId)); } return success; @@ -43,66 +43,67 @@ public String badgeIn(ObjectContext ctx, String station) { } // start this journey - ctx.set(ONGOING_JOURNEY, station); + state.set(ONGOING_JOURNEY, station); // ensure this journey finishes by end of operations - CardTrackerClient.fromContext(ctx, cardId) - .send() - .finalizeAllJourneys(delayTillEndOfDay()); + Restate.virtualObjectHandle(CardTracker.class, cardId) + .send(CardTracker::finalizeAllJourneys, delayTillEndOfDay()); return "OK"; } @Handler - public String badgeOut(ObjectContext ctx, String exitStation) { - final String cardId = ctx.key(); + public String badgeOut(String exitStation) { + final String cardId = Restate.key(); + var state = Restate.state(); - final Optional startingStation = ctx.get(ONGOING_JOURNEY); + final Optional startingStation = state.get(ONGOING_JOURNEY); if (startingStation.isEmpty()) { return "BLOCKED"; } - ctx.clear(ONGOING_JOURNEY); + state.clear(ONGOING_JOURNEY); - final DailyFare fare = ctx.get(TOTAL_FARE_TODAY).orElseGet(DailyFare::new); + final DailyFare fare = state.get(TOTAL_FARE_TODAY).orElseGet(DailyFare::new); final long tripPrice = fare.addTrip(startingStation.get(), exitStation); - ctx.set(TOTAL_FARE_TODAY, fare); + state.set(TOTAL_FARE_TODAY, fare); - return tryCharge(ctx, cardId, tripPrice) ? "OK" : "BLOCKED"; + return tryCharge(cardId, tripPrice) ? "OK" : "BLOCKED"; } @Handler - public void finalizeAllJourneys(ObjectContext ctx) { + public void finalizeAllJourneys() { + var state = Restate.state(); // Executes at the end of the day // If the card still has an ongoing journey, end it and charge the day ticket - ctx.get(ONGOING_JOURNEY).ifPresent((String startStation) -> { - String cardId = ctx.key(); - DailyFare fare = ctx.get(TOTAL_FARE_TODAY).orElseGet(DailyFare::new); + state.get(ONGOING_JOURNEY).ifPresent((String startStation) -> { + String cardId = Restate.key(); + DailyFare fare = state.get(TOTAL_FARE_TODAY).orElseGet(DailyFare::new); long remainderToPay = fare.upgradeToDayTicket(); - tryCharge(ctx, cardId, remainderToPay); - ctx.clear(ONGOING_JOURNEY); + tryCharge(cardId, remainderToPay); + state.clear(ONGOING_JOURNEY); }); - ctx.clear(TOTAL_FARE_TODAY); + state.clear(TOTAL_FARE_TODAY); } @Shared - public boolean isBlocked(SharedObjectContext ctx) { - return !ctx.get(AUTHORIZED).orElse(true); + public boolean isBlocked() { + return !Restate.state().get(AUTHORIZED).orElse(true); } @Handler - public void unblock(ObjectContext ctx) { - ctx.clear(AUTHORIZED); + public void unblock() { + Restate.state().clear(AUTHORIZED); } - private boolean tryCharge(ObjectContext ctx, String cardRef, long amountCents) { + private boolean tryCharge(String cardRef, long amountCents) { if (amountCents == 0) { return true; } - boolean paymentSuccess = Payments.chargeCard(ctx, cardRef, amountCents); + boolean paymentSuccess = Payments.chargeCard(cardRef, amountCents); if (!paymentSuccess) { - ctx.set(AUTHORIZED, false); - ctx.run("block card", () -> CardStatusServiceApi.tagCardAsBlocked(cardRef)); + Restate.state().set(AUTHORIZED, false); + Restate.run("block card", () -> CardStatusServiceApi.tagCardAsBlocked(cardRef)); } return paymentSuccess; } diff --git a/java/end-to-end-applications/subway-fare-calculator/src/main/java/dev/restate/example/Payments.java b/java/end-to-end-applications/subway-fare-calculator/src/main/java/dev/restate/example/Payments.java index 09e33b3c..56d613cc 100644 --- a/java/end-to-end-applications/subway-fare-calculator/src/main/java/dev/restate/example/Payments.java +++ b/java/end-to-end-applications/subway-fare-calculator/src/main/java/dev/restate/example/Payments.java @@ -1,7 +1,7 @@ package dev.restate.example; import dev.restate.example.apis.PaymentApi; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.common.RetryPolicy; import dev.restate.sdk.common.TerminalException; @@ -15,17 +15,17 @@ public class Payments { .setMaxAttempts(AUTH_ATTEMPTS) .setInitialDelay(ATTEMPT_DELAY); - public static boolean authorizeCard(Context ctx, String cardRef) { + public static boolean authorizeCard(String cardRef) { try { - return ctx.run("auth attempt", Boolean.TYPE, retryThreeTimes, () -> PaymentApi.runAuthorization(cardRef)); + return Restate.run("auth attempt", Boolean.TYPE, retryThreeTimes, () -> PaymentApi.runAuthorization(cardRef)); } catch (TerminalException e) { return false; } } - public static boolean chargeCard(Context ctx, String cardRef, long amountCents) { + public static boolean chargeCard(String cardRef, long amountCents) { try { - return ctx.run("charge attempt", Boolean.TYPE, retryThreeTimes, () -> PaymentApi.makePayment(cardRef, amountCents)); + return Restate.run("charge attempt", Boolean.TYPE, retryThreeTimes, () -> PaymentApi.makePayment(cardRef, amountCents)); } catch (TerminalException e) { return false; } diff --git a/java/end-to-end-applications/workflow-interpreter/.mvn/wrapper/maven-wrapper.properties b/java/end-to-end-applications/workflow-interpreter/.mvn/wrapper/maven-wrapper.properties index d58dfb70..216df058 100644 --- a/java/end-to-end-applications/workflow-interpreter/.mvn/wrapper/maven-wrapper.properties +++ b/java/end-to-end-applications/workflow-interpreter/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/java/end-to-end-applications/workflow-interpreter/mvnw b/java/end-to-end-applications/workflow-interpreter/mvnw index 19529ddf..bd8896bf 100755 --- a/java/end-to-end-applications/workflow-interpreter/mvnw +++ b/java/end-to-end-applications/workflow-interpreter/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Optional ENV vars # ----------------- @@ -105,14 +105,17 @@ trim() { printf "%s" "${1}" | tr -d '[:space:]' } +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) @@ -130,7 +133,7 @@ maven-mvnd-*bin.*) distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME @@ -227,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then @@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" diff --git a/java/end-to-end-applications/workflow-interpreter/mvnw.cmd b/java/end-to-end-applications/workflow-interpreter/mvnw.cmd index 249bdf38..92450f93 100644 --- a/java/end-to-end-applications/workflow-interpreter/mvnw.cmd +++ b/java/end-to-end-applications/workflow-interpreter/mvnw.cmd @@ -19,7 +19,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @@ -40,7 +40,7 @@ @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> @@ -73,16 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" + +$MAVEN_M2_PATH = "$HOME/.m2" if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" } -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { @@ -134,7 +148,33 @@ if ($distributionSha256Sum) { # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { diff --git a/java/end-to-end-applications/workflow-interpreter/pom.xml b/java/end-to-end-applications/workflow-interpreter/pom.xml index d130f25e..551963a8 100644 --- a/java/end-to-end-applications/workflow-interpreter/pom.xml +++ b/java/end-to-end-applications/workflow-interpreter/pom.xml @@ -27,10 +27,10 @@ - 17 + 25 4.1.124.Final - 2.7.0 + 2.9.0 @@ -64,23 +64,44 @@ + + + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + select-jdk-toolchain + + + + + [25,) + + org.springframework.boot spring-boot-maven-plugin + + + --enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow + maven-compiler-plugin true - - - dev.restate - sdk-api-gen - ${restate.version} - - + + + + maven-surefire-plugin + + + --enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow diff --git a/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/ImageProcessingWorkflow.java b/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/ImageProcessingWorkflow.java index 0ce017a1..061c9d39 100644 --- a/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/ImageProcessingWorkflow.java +++ b/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/ImageProcessingWorkflow.java @@ -5,9 +5,9 @@ import com.example.restatestarter.types.WorkflowStepProcessor; import dev.restate.common.Target; import dev.restate.common.Request; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Workflow; -import dev.restate.sdk.springboot.RestateWorkflow; +import dev.restate.sdk.springboot.RestateComponent; import dev.restate.sdk.common.StateKey; import dev.restate.serde.TypeTag; @@ -15,26 +15,28 @@ import static com.example.restatestarter.utils.WorkflowProcessingUtils.*; -@RestateWorkflow +@RestateComponent +@Workflow public class ImageProcessingWorkflow { private static final StateKey STATUS = StateKey.of("status", WorkflowStatus.class); @Workflow - public WorkflowStatus run(WorkflowContext ctx, List wfSteps) throws Exception { + public WorkflowStatus run(List wfSteps) throws Exception { validateWorkflowDefinition(wfSteps); - String imgName = ctx.random().nextUUID().toString(); + String imgName = Restate.random().nextUUID().toString(); // Add the image input and output names for each step (uuid + step number) List enrichedWfSteps = addImgPathToSteps(wfSteps, imgName); + var state = Restate.state(); WorkflowStatus status = new WorkflowStatus("Processing", imgName, Collections.emptyList()); - ctx.set(STATUS, status); + state.set(STATUS, status); for (WorkflowStep step : enrichedWfSteps) { WorkflowStepProcessor processor = getProcessorStepFromRegistry(step.action()); - String result = ctx.call( + String result = Restate.call( Request.of( Target.service(processor.service(), processor.method()), TypeTag.of(WorkflowStep.class), @@ -43,11 +45,11 @@ public WorkflowStatus run(WorkflowContext ctx, List wfSteps) throw ) ).await(); status = status.withNewOutput(result); - ctx.set(STATUS, status); + state.set(STATUS, status); } status = status.withStatus("Finished"); - ctx.set(STATUS, status); + state.set(STATUS, status); return status; } -} \ No newline at end of file +} diff --git a/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/PuppeteerService.java b/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/PuppeteerService.java index fe09cc43..42fdf3bb 100644 --- a/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/PuppeteerService.java +++ b/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/PuppeteerService.java @@ -1,9 +1,10 @@ package com.example.restatestarter; import com.example.restatestarter.types.WorkflowStep; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.springboot.RestateService; +import dev.restate.sdk.annotation.Service; +import dev.restate.sdk.springboot.RestateComponent; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; @@ -15,15 +16,16 @@ import java.nio.file.Files; import java.nio.file.Paths; -@RestateService +@RestateComponent +@Service public class PuppeteerService { @Handler - public String run(Context ctx, WorkflowStep wf) throws Exception { + public String run(WorkflowStep wf) throws Exception { System.out.println("Taking screenshot of website with parameters: " + wf); String screenshotUrl = (String) wf.parameters().get("url"); - ctx.run(() -> takeWebsiteScreenshot(wf.imgOutputPath(), screenshotUrl)); + Restate.run("takeScreenshot", () -> takeWebsiteScreenshot(wf.imgOutputPath(), screenshotUrl)); return "[Took screenshot of website with url: " + screenshotUrl + "]"; } @@ -41,4 +43,4 @@ private void takeWebsiteScreenshot(String imgOutputPath, String screenshotUrl) t driver.quit(); } -} \ No newline at end of file +} diff --git a/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/TransformerService.java b/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/TransformerService.java index 858afa09..df65cc23 100644 --- a/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/TransformerService.java +++ b/java/end-to-end-applications/workflow-interpreter/src/main/java/com/example/restatestarter/TransformerService.java @@ -1,23 +1,25 @@ package com.example.restatestarter; import com.example.restatestarter.types.WorkflowStep; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.springboot.RestateService; +import dev.restate.sdk.annotation.Service; +import dev.restate.sdk.springboot.RestateComponent; import java.awt.image.BufferedImage; import static com.example.restatestarter.utils.TransformerUtils.*; -@RestateService +@RestateComponent +@Service public class TransformerService { @Handler - public String rotate(Context ctx, WorkflowStep wfStep) throws Exception { + public String rotate(WorkflowStep wfStep) throws Exception { int angle = (int) wfStep.parameters().get("angle"); System.out.println("Rotating image with angle: " + angle); - ctx.run(() -> { + Restate.run("rotate", () -> { BufferedImage image = getImage(wfStep.imgInputPath()); BufferedImage rotatedImage = rotateImage(image, angle); writeImage(rotatedImage, wfStep.imgOutputPath()); @@ -27,11 +29,11 @@ public String rotate(Context ctx, WorkflowStep wfStep) throws Exception { } @Handler - public String blur(Context ctx, WorkflowStep wfStep) throws Exception { + public String blur(WorkflowStep wfStep) throws Exception { int blur = (int) wfStep.parameters().get("blur"); System.out.println("Blurring image with parameter " + blur); - ctx.run(() -> { + Restate.run("blur", () -> { BufferedImage image = getImage(wfStep.imgInputPath()); BufferedImage blurredImage = blurImage(image, blur); writeImage(blurredImage, wfStep.imgOutputPath()); @@ -39,4 +41,4 @@ public String blur(Context ctx, WorkflowStep wfStep) throws Exception { return "[Blurred image with strength param " + blur + "]"; } -} \ No newline at end of file +} diff --git a/java/integrations/java-gradle-lambda-cdk/README.md b/java/integrations/java-gradle-lambda-cdk/README.md index bf279352..b909410e 100644 --- a/java/integrations/java-gradle-lambda-cdk/README.md +++ b/java/integrations/java-gradle-lambda-cdk/README.md @@ -33,7 +33,7 @@ For more information on CDK, please see [Getting started with the AWS CDK](https * npm * gradle -* JDK >= 21 +* JDK >= 25 * AWS account, bootstrapped for CDK use * valid AWS credentials with sufficient privileges to create the necessary resources * an existing [Restate Cloud](https://restate.dev) environment (environment id + API key) diff --git a/java/integrations/java-gradle-lambda-cdk/cdk/lambda-jvm-cdk-stack.ts b/java/integrations/java-gradle-lambda-cdk/cdk/lambda-jvm-cdk-stack.ts index c6ea1e00..5da78ddd 100644 --- a/java/integrations/java-gradle-lambda-cdk/cdk/lambda-jvm-cdk-stack.ts +++ b/java/integrations/java-gradle-lambda-cdk/cdk/lambda-jvm-cdk-stack.ts @@ -20,11 +20,15 @@ export class LambdaJvmCdkStack extends cdk.Stack { super(scope, id, props); const handler: lambda.Function = new lambda.Function(this, "GreeterService", { - runtime: lambda.Runtime.JAVA_21, + runtime: lambda.Runtime.JAVA_25, architecture: lambda.Architecture.ARM_64, code: lambda.Code.fromAsset("lambda/build/distributions/lambda.zip"), handler: "my.example.LambdaHandler", timeout: cdk.Duration.seconds(10), + environment: { + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + JAVA_TOOL_OPTIONS: "--enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow", + }, loggingFormat: lambda.LoggingFormat.JSON, applicationLogLevelV2: lambda.ApplicationLogLevel.DEBUG, systemLogLevelV2: lambda.SystemLogLevel.INFO, diff --git a/java/integrations/java-gradle-lambda-cdk/lambda/build.gradle.kts b/java/integrations/java-gradle-lambda-cdk/lambda/build.gradle.kts index 6977fc10..0c7ecdcf 100644 --- a/java/integrations/java-gradle-lambda-cdk/lambda/build.gradle.kts +++ b/java/integrations/java-gradle-lambda-cdk/lambda/build.gradle.kts @@ -6,12 +6,9 @@ repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - // Annotation processor - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-java-lambda:$restateVersion") @@ -29,7 +26,7 @@ dependencies { // Setup Java compiler target java { toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(25)) } } diff --git a/java/integrations/java-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.jar b/java/integrations/java-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.jar index 033e24c4..b1b8ef56 100644 Binary files a/java/integrations/java-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.jar and b/java/integrations/java-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/integrations/java-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.properties b/java/integrations/java-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.properties index a80b22ce..a9db1155 100644 --- a/java/integrations/java-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.properties +++ b/java/integrations/java-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/integrations/java-gradle-lambda-cdk/lambda/gradlew b/java/integrations/java-gradle-lambda-cdk/lambda/gradlew index fcb6fca1..249efbb0 100755 --- a/java/integrations/java-gradle-lambda-cdk/lambda/gradlew +++ b/java/integrations/java-gradle-lambda-cdk/lambda/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -15,10 +15,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -27,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -55,7 +57,7 @@ # 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 +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -111,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -144,7 +146,7 @@ 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=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -169,7 +171,6 @@ fi # 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" ) @@ -201,16 +202,15 @@ 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, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_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 \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/java/integrations/java-gradle-lambda-cdk/lambda/gradlew.bat b/java/integrations/java-gradle-lambda-cdk/lambda/gradlew.bat index 93e3f59f..8508ef68 100644 --- a/java/integrations/java-gradle-lambda-cdk/lambda/gradlew.bat +++ b/java/integrations/java-gradle-lambda-cdk/lambda/gradlew.bat @@ -1,92 +1,82 @@ -@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 +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +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. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/integrations/java-gradle-lambda-cdk/lambda/src/main/java/my/example/Greeter.java b/java/integrations/java-gradle-lambda-cdk/lambda/src/main/java/my/example/Greeter.java index dda30ced..7ee68d3b 100644 --- a/java/integrations/java-gradle-lambda-cdk/lambda/src/main/java/my/example/Greeter.java +++ b/java/integrations/java-gradle-lambda-cdk/lambda/src/main/java/my/example/Greeter.java @@ -3,7 +3,7 @@ import static my.example.Utils.sendNotification; import static my.example.Utils.sendReminder; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import java.time.Duration; @@ -12,12 +12,12 @@ public class Greeter { @Handler - public String greet(Context ctx, String name) { + public String greet(String name) { // Durably execute a set of steps; resilient against failures - String greetingId = ctx.random().nextUUID().toString(); - ctx.run(() -> sendNotification(greetingId, name)); - ctx.sleep(Duration.ofMillis(1000)); - ctx.run(() -> sendReminder(greetingId)); + String greetingId = Restate.random().nextUUID().toString(); + Restate.run("send-notification", () -> sendNotification(greetingId, name)); + Restate.sleep(Duration.ofMillis(1000)); + Restate.run("send-reminder", () -> sendReminder(greetingId)); // Respond to caller return "You said hi to " + name + "!"; diff --git a/java/integrations/java-spring/build.gradle.kts b/java/integrations/java-spring/build.gradle.kts index e5b23feb..d8319f49 100644 --- a/java/integrations/java-spring/build.gradle.kts +++ b/java/integrations/java-spring/build.gradle.kts @@ -3,7 +3,7 @@ plugins { application id("org.springframework.boot") version "3.3.1" id("io.spring.dependency-management") version "1.1.5" - id("com.diffplug.spotless") version "6.25.0" + id("com.diffplug.spotless") version "8.8.0" } group = "dev.restate.examples" @@ -13,15 +13,13 @@ repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { implementation("org.springframework.boot:spring-boot-starter") testImplementation("org.springframework.boot:spring-boot-starter-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") - implementation("org.springframework.boot", "spring-boot-starter-data-jpa") // Restate SDK @@ -35,7 +33,7 @@ dependencies { java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) } } @@ -46,6 +44,13 @@ application { tasks.withType { useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} + +tasks.named("bootRun") { + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } spotless { diff --git a/java/integrations/java-spring/gradle/wrapper/gradle-wrapper.jar b/java/integrations/java-spring/gradle/wrapper/gradle-wrapper.jar index e6441136..b1b8ef56 100644 Binary files a/java/integrations/java-spring/gradle/wrapper/gradle-wrapper.jar and b/java/integrations/java-spring/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/integrations/java-spring/gradle/wrapper/gradle-wrapper.properties b/java/integrations/java-spring/gradle/wrapper/gradle-wrapper.properties index 37f853b1..a9db1155 100644 --- a/java/integrations/java-spring/gradle/wrapper/gradle-wrapper.properties +++ b/java/integrations/java-spring/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/integrations/java-spring/gradlew b/java/integrations/java-spring/gradlew index b740cf13..249efbb0 100755 --- a/java/integrations/java-spring/gradlew +++ b/java/integrations/java-spring/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -15,10 +15,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -27,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # 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 +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -170,7 +171,6 @@ fi # 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" ) @@ -203,15 +203,14 @@ fi 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, +# * DEFAULT_JVM_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 \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/java/integrations/java-spring/gradlew.bat b/java/integrations/java-spring/gradlew.bat index 7101f8e4..8508ef68 100644 --- a/java/integrations/java-spring/gradlew.bat +++ b/java/integrations/java-spring/gradlew.bat @@ -13,16 +13,18 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -49,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -63,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/integrations/java-spring/src/main/java/dev/restate/examples/ProductService.java b/java/integrations/java-spring/src/main/java/dev/restate/examples/ProductService.java index 361d5fb5..deaf0485 100644 --- a/java/integrations/java-spring/src/main/java/dev/restate/examples/ProductService.java +++ b/java/integrations/java-spring/src/main/java/dev/restate/examples/ProductService.java @@ -2,12 +2,14 @@ import dev.restate.examples.model.Product; import dev.restate.examples.repository.ProductRepository; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.springboot.RestateVirtualObject; +import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.TerminalException; +import dev.restate.sdk.springboot.RestateComponent; -@RestateVirtualObject +@RestateComponent +@VirtualObject public class ProductService { private final ProductRepository productRepository; @@ -17,21 +19,23 @@ public ProductService(ProductRepository productRepository) { } @Handler - public Product getProductInformation(ObjectContext ctx) { - return ctx.run( + public Product getProductInformation() { + return Restate.run( + "getProduct", Product.class, () -> productRepository - .findById(ctx.key()) + .findById(Restate.key()) .orElseThrow(() -> new TerminalException("Product does not exist"))); } @Handler - public boolean reserve(ObjectContext ctx) { - String productId = ctx.key(); + public boolean reserve() { + String productId = Restate.key(); Product product = - ctx.run( + Restate.run( + "getProduct", Product.class, () -> productRepository @@ -42,21 +46,26 @@ public boolean reserve(ObjectContext ctx) { return false; } - ctx.run(() -> productRepository.updateQuantity(productId, product.getQuantity() - 1)); + Restate.run( + "updateQuantity", + () -> productRepository.updateQuantity(productId, product.getQuantity() - 1)); return true; } @Handler - public void release(ObjectContext ctx) { - String productId = ctx.key(); + public void release() { + String productId = Restate.key(); Product product = - ctx.run( + Restate.run( + "getProduct", Product.class, () -> productRepository .findById(productId) .orElseThrow(() -> new TerminalException("Product does not exist"))); - ctx.run(() -> productRepository.updateQuantity(productId, product.getQuantity() + 1)); + Restate.run( + "updateQuantity", + () -> productRepository.updateQuantity(productId, product.getQuantity() + 1)); } } diff --git a/java/patterns-use-cases/build.gradle.kts b/java/patterns-use-cases/build.gradle.kts index 6b2a85c7..659a3955 100644 --- a/java/patterns-use-cases/build.gradle.kts +++ b/java/patterns-use-cases/build.gradle.kts @@ -1,18 +1,16 @@ plugins { java application - id("com.diffplug.spotless") version "6.25.0" + id("com.diffplug.spotless") version "8.8.0" } repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-java-http:$restateVersion") @@ -36,7 +34,7 @@ dependencies { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -47,6 +45,8 @@ application { } else { mainClass.set("my.example.dataupload.DataUploadService") } + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } tasks.withType { @@ -57,6 +57,8 @@ tasks.withType { tasks.named("test") { useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } spotless { diff --git a/java/patterns-use-cases/gradle/wrapper/gradle-wrapper.jar b/java/patterns-use-cases/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/java/patterns-use-cases/gradle/wrapper/gradle-wrapper.jar and b/java/patterns-use-cases/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/patterns-use-cases/gradle/wrapper/gradle-wrapper.properties b/java/patterns-use-cases/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/java/patterns-use-cases/gradle/wrapper/gradle-wrapper.properties +++ b/java/patterns-use-cases/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/patterns-use-cases/gradlew b/java/patterns-use-cases/gradlew index faf93008..249efbb0 100755 --- a/java/patterns-use-cases/gradlew +++ b/java/patterns-use-cases/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/java/patterns-use-cases/gradlew.bat b/java/patterns-use-cases/gradlew.bat index 9b42019c..8508ef68 100644 --- a/java/patterns-use-cases/gradlew.bat +++ b/java/patterns-use-cases/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/patterns-use-cases/src/main/java/my/example/batcher/BatchReceiver.java b/java/patterns-use-cases/src/main/java/my/example/batcher/BatchReceiver.java index f2599435..679074f6 100644 --- a/java/patterns-use-cases/src/main/java/my/example/batcher/BatchReceiver.java +++ b/java/patterns-use-cases/src/main/java/my/example/batcher/BatchReceiver.java @@ -1,6 +1,5 @@ package my.example.batcher; -import dev.restate.sdk.Context; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import java.util.List; @@ -13,7 +12,7 @@ public class BatchReceiver { private static final Logger LOG = LogManager.getLogger(BatchReceiver.class); @Handler - public void receive(Context ctx, List items) { + public void receive(List items) { LOG.info("Received batch: {}", items); } } diff --git a/java/patterns-use-cases/src/main/java/my/example/batcher/Batcher.java b/java/patterns-use-cases/src/main/java/my/example/batcher/Batcher.java index e40a6de1..85f19bb3 100644 --- a/java/patterns-use-cases/src/main/java/my/example/batcher/Batcher.java +++ b/java/patterns-use-cases/src/main/java/my/example/batcher/Batcher.java @@ -1,7 +1,7 @@ package my.example.batcher; import dev.restate.sdk.InvocationHandle; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; @@ -24,8 +24,9 @@ public class Batcher { record BatcherState(List items, String expireInvocationId) {} @Handler - public void receive(ObjectContext ctx, String item) { - ctx.get(STATE) + public void receive(String item) { + Restate.state() + .get(STATE) .ifPresentOrElse( state -> { LOG.info("Adding item {} to existing batch", item); @@ -33,30 +34,32 @@ public void receive(ObjectContext ctx, String item) { if (state.items().size() == MAX_BATCH) { LOG.info("Sending batch as it reached {} items", MAX_BATCH); // cancel scheduled sending, since we are sending due to item count - ctx.invocationHandle(state.expireInvocationId()).cancel(); - BatchReceiverClient.fromContext(ctx).receive(state.items()); - ctx.clearAll(); + Restate.invocationHandle(state.expireInvocationId()).cancel(); + Restate.service(BatchReceiver.class).receive(state.items()); + Restate.state().clearAll(); } else { // write back updated state - ctx.set(STATE, state); + Restate.state().set(STATE, state); } }, () -> { LOG.info("Adding item to new batch, will send in at most {}", MAX_BATCH_WAIT); InvocationHandle invocationId = - BatcherClient.fromContext(ctx, ctx.key()).send().expire(MAX_BATCH_WAIT); - ctx.set(STATE, new BatcherState(List.of(item), invocationId.invocationId())); + Restate.virtualObjectHandle(Batcher.class, Restate.key()) + .send(Batcher::expire, MAX_BATCH_WAIT); + Restate.state() + .set(STATE, new BatcherState(List.of(item), invocationId.invocationId())); }); } @Handler - public void expire(ObjectContext ctx) { + public void expire() { // if there is no state expire should never be called (meaning a bug), // so throw TerminalException since there is no recovery - var state = ctx.get(STATE).orElseThrow(TerminalException::new); + var state = Restate.state().get(STATE).orElseThrow(TerminalException::new); LOG.info("Sending batch with {} items as the timer fired", state.items().size()); - BatchReceiverClient.fromContext(ctx).receive(state.items()); - ctx.clearAll(); + Restate.service(BatchReceiver.class).receive(state.items()); + Restate.state().clearAll(); } public static void main(String[] args) { diff --git a/java/patterns-use-cases/src/main/java/my/example/concurrenttasks/EmailVerification.java b/java/patterns-use-cases/src/main/java/my/example/concurrenttasks/EmailVerification.java index 10e372ad..9c1065d3 100644 --- a/java/patterns-use-cases/src/main/java/my/example/concurrenttasks/EmailVerification.java +++ b/java/patterns-use-cases/src/main/java/my/example/concurrenttasks/EmailVerification.java @@ -3,7 +3,7 @@ import static my.example.concurrenttasks.utils.EmailClient.sendReminder; import static my.example.concurrenttasks.utils.EmailClient.sendVerificationEmail; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.Select; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; @@ -28,7 +28,7 @@ * or restarts, it resumes exactly where it left off - no lost emails, no duplicate * reminders, no forgotten timeouts. * - * 2. DURABLE FUTURES: The ctx.awakeable() creates a durable future that can be + * 2. DURABLE FUTURES: The Restate.awakeable() creates a durable future that can be * resolved from external systems (like a webhook when user clicks verification link). * This survives service restarts and infrastructure failures. * @@ -54,14 +54,14 @@ public class EmailVerification { public record VerifyEmailRequest(String email, String userId) {} @Handler - public String verifyEmail(Context ctx, VerifyEmailRequest req) { - var confirmationFuture = ctx.awakeable(Boolean.class); - ctx.run("send email", () -> sendVerificationEmail(req.email(), confirmationFuture.id())); + public String verifyEmail(VerifyEmailRequest req) { + var confirmationFuture = Restate.awakeable(Boolean.class); + Restate.run("send email", () -> sendVerificationEmail(req.email(), confirmationFuture.id())); - var verificationTimeout = ctx.timer(Duration.ofDays(1)); + var verificationTimeout = Restate.timer(Duration.ofDays(1)); while (true) { - var reminderTimer = ctx.timer(Duration.ofSeconds(10)); + var reminderTimer = Restate.timer(Duration.ofSeconds(10)); var selected = Select.select() @@ -76,7 +76,7 @@ public String verifyEmail(Context ctx, VerifyEmailRequest req) { case "failure": return "Email rejected"; case "reminder": - ctx.run("send reminder", () -> sendReminder(req.email(), confirmationFuture.id())); + Restate.run("send reminder", () -> sendReminder(req.email(), confirmationFuture.id())); break; case "timeout": throw new TerminalException("Verification timed out"); diff --git a/java/patterns-use-cases/src/main/java/my/example/cron/Cron.java b/java/patterns-use-cases/src/main/java/my/example/cron/Cron.java index 4ddd30ad..705482af 100644 --- a/java/patterns-use-cases/src/main/java/my/example/cron/Cron.java +++ b/java/patterns-use-cases/src/main/java/my/example/cron/Cron.java @@ -7,9 +7,7 @@ import com.cronutils.parser.CronParser; import dev.restate.common.Request; import dev.restate.common.Target; -import dev.restate.sdk.Context; -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.SharedObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.*; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.common.TerminalException; @@ -46,11 +44,11 @@ public record JobInfo(JobRequest request, String nextExecutionTime, String nextE @Service public static class JobInitiator { @Handler - public String create(Context ctx, JobRequest request) { + public String create(JobRequest request) { // Create a new job ID and initiate the cron job object for that ID // We can then address this job object by its ID - var jobId = ctx.random().nextUUID().toString(); - var cronJob = CronJobClient.fromContext(ctx, jobId).initiate(request).await(); + var jobId = Restate.random().nextUUID().toString(); + var cronJob = Restate.virtualObject(Job.class, jobId).initiate(request); return String.format( "Job created with ID %s and next execution time %s", jobId, cronJob.nextExecutionTime()); } @@ -65,37 +63,41 @@ public static class Job { new CronParser(CronDefinitionBuilder.instanceDefinitionFor(UNIX)); @Handler - public JobInfo initiate(ObjectContext ctx, JobRequest request) { - if (ctx.get(JOB_STATE).isPresent()) { + public JobInfo initiate(JobRequest request) { + if (Restate.state().get(JOB_STATE).isPresent()) { throw new TerminalException("Job already exists for this ID"); } - return scheduleNextExecution(ctx, request); + return scheduleNextExecution(request); } @Handler - public void execute(ObjectContext ctx) { + public void execute() { JobRequest request = - ctx.get(JOB_STATE).orElseThrow(() -> new TerminalException("Job not found")).request; + Restate.state() + .get(JOB_STATE) + .orElseThrow(() -> new TerminalException("Job not found")) + .request; - executeTask(ctx, request); - scheduleNextExecution(ctx, request); + executeTask(request); + scheduleNextExecution(request); } @Handler - public void cancel(ObjectContext ctx) { - ctx.get(JOB_STATE) - .ifPresent(jobState -> ctx.invocationHandle(jobState.nextExecutionId).cancel()); + public void cancel() { + Restate.state() + .get(JOB_STATE) + .ifPresent(jobState -> Restate.invocationHandle(jobState.nextExecutionId).cancel()); // Clear the job state - ctx.clearAll(); + Restate.state().clearAll(); } @Shared - public Optional getInfo(SharedObjectContext ctx) { - return ctx.get(JOB_STATE); + public Optional getInfo() { + return Restate.state().get(JOB_STATE); } - private void executeTask(ObjectContext ctx, JobRequest job) { + private void executeTask(JobRequest job) { Target target = (job.key.isPresent()) ? Target.virtualObject(job.service, job.method, job.key.get()) @@ -105,10 +107,10 @@ private void executeTask(ObjectContext ctx, JobRequest job) { ? Request.of( target, TypeTag.of(String.class), TypeTag.of(Void.class), job.payload.get()) : Request.of(target, new byte[0]); - ctx.send(request); + Restate.send(request); } - private JobInfo scheduleNextExecution(ObjectContext ctx, JobRequest request) { + private JobInfo scheduleNextExecution(JobRequest request) { // Parse cron expression ExecutionTime executionTime; try { @@ -118,7 +120,7 @@ private JobInfo scheduleNextExecution(ObjectContext ctx, JobRequest request) { } // Calculate next execution time - var now = ctx.run(ZonedDateTime.class, ZonedDateTime::now); + var now = Restate.run("now", ZonedDateTime.class, ZonedDateTime::now); var delay = executionTime .timeToNextExecution(now) @@ -129,12 +131,12 @@ private JobInfo scheduleNextExecution(ObjectContext ctx, JobRequest request) { .orElseThrow(() -> new TerminalException("Cannot determine next execution time")); // Schedule next execution for this job - String thisJobId = ctx.key(); // This got generated by the CronJobInitiator - var handle = CronJobClient.fromContext(ctx, thisJobId).send().execute(delay); + String thisJobId = Restate.key(); // This got generated by the CronJobInitiator + var handle = Restate.virtualObjectHandle(Job.class, thisJobId).send(Job::execute, delay); // Save job state var jobState = new JobInfo(request, next.toString(), handle.invocationId()); - ctx.set(JOB_STATE, jobState); + Restate.state().set(JOB_STATE, jobState); return jobState; } } diff --git a/java/patterns-use-cases/src/main/java/my/example/cron/TaskService.java b/java/patterns-use-cases/src/main/java/my/example/cron/TaskService.java index 04a3ed2f..47be6206 100644 --- a/java/patterns-use-cases/src/main/java/my/example/cron/TaskService.java +++ b/java/patterns-use-cases/src/main/java/my/example/cron/TaskService.java @@ -1,6 +1,5 @@ package my.example.cron; -import dev.restate.sdk.Context; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.endpoint.Endpoint; @@ -16,7 +15,7 @@ public class TaskService { This is a task stub to be able to demo the cron service. */ @Handler - public void executeTask(Context ctx, String task) { + public void executeTask(String task) { // Here you would implement the logic to execute the task // For example, you could call another service or perform some computation logger.info("Executing task with payload: {}", task); diff --git a/java/patterns-use-cases/src/main/java/my/example/durablerpc/MyClient.java b/java/patterns-use-cases/src/main/java/my/example/durablerpc/MyClient.java index 95da77ac..cfbec19d 100644 --- a/java/patterns-use-cases/src/main/java/my/example/durablerpc/MyClient.java +++ b/java/patterns-use-cases/src/main/java/my/example/durablerpc/MyClient.java @@ -1,6 +1,7 @@ package my.example.durablerpc; import dev.restate.client.Client; +import dev.restate.common.InvocationOptions; import java.util.Optional; public class MyClient { @@ -13,9 +14,11 @@ public boolean reserveProduct(String productId, String reservationId) { // Durable RPC call to the product service // Restate registers the request and makes sure runs to completion exactly once boolean reserved = - ProductServiceClient.fromClient(restateClient, productId) + restateClient + .virtualObjectHandle(ProductService.class, productId) // Restate deduplicates requests with the same idempotency key - .reserve(opts -> opts.idempotencyKey(reservationId)); + .call(ProductService::reserve, InvocationOptions.idempotencyKey(reservationId)) + .response(); return reserved; } diff --git a/java/patterns-use-cases/src/main/java/my/example/durablerpc/ProductService.java b/java/patterns-use-cases/src/main/java/my/example/durablerpc/ProductService.java index 8b0bc652..147100c6 100644 --- a/java/patterns-use-cases/src/main/java/my/example/durablerpc/ProductService.java +++ b/java/patterns-use-cases/src/main/java/my/example/durablerpc/ProductService.java @@ -1,6 +1,6 @@ package my.example.durablerpc; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; @@ -20,13 +20,13 @@ public class ProductService { private static final StateKey RESERVED = StateKey.of("reserved", Boolean.TYPE); @Handler - public boolean reserve(ObjectContext ctx) { - if (ctx.get(RESERVED).orElse(false)) { + public boolean reserve() { + if (Restate.state().get(RESERVED).orElse(false)) { LOG.info("Product already reserved"); return false; } LOG.info("Reserving product"); - ctx.set(RESERVED, true); + Restate.state().set(RESERVED, true); return true; } diff --git a/java/patterns-use-cases/src/main/java/my/example/eventenrichment/PackageTracker.java b/java/patterns-use-cases/src/main/java/my/example/eventenrichment/PackageTracker.java index 6c83c099..57aa563a 100644 --- a/java/patterns-use-cases/src/main/java/my/example/eventenrichment/PackageTracker.java +++ b/java/patterns-use-cases/src/main/java/my/example/eventenrichment/PackageTracker.java @@ -1,7 +1,6 @@ package my.example.eventenrichment; -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.SharedObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.VirtualObject; @@ -23,26 +22,30 @@ public class PackageTracker { // Called first by the seller over HTTP @Handler - public void registerPackage(ObjectContext ctx, PackageInfo packageInfo) { + public void registerPackage(PackageInfo packageInfo) { // Store the package details in the state - ctx.set(PACKAGE_INFO, packageInfo); + Restate.state().set(PACKAGE_INFO, packageInfo); } // Connected to a Kafka topic for real-time location updates @Handler - public void updateLocation(ObjectContext ctx, LocationUpdate locationUpdate) { + public void updateLocation(LocationUpdate locationUpdate) { var packageInfo = - ctx.get(PACKAGE_INFO).orElseThrow(() -> new TerminalException("Package not found")); + Restate.state() + .get(PACKAGE_INFO) + .orElseThrow(() -> new TerminalException("Package not found")); // Update the package info with the new location packageInfo.addLocation(locationUpdate); - ctx.set(PACKAGE_INFO, packageInfo); + Restate.state().set(PACKAGE_INFO, packageInfo); } // Called by the delivery dashboard to get the package details @Shared - public PackageInfo getPackageInfo(SharedObjectContext ctx) { - return ctx.get(PACKAGE_INFO).orElseThrow(() -> new TerminalException("Package not found")); + public PackageInfo getPackageInfo() { + return Restate.state() + .get(PACKAGE_INFO) + .orElseThrow(() -> new TerminalException("Package not found")); } public static void main(String[] args) { diff --git a/java/patterns-use-cases/src/main/java/my/example/eventtransactions/UserFeed.java b/java/patterns-use-cases/src/main/java/my/example/eventtransactions/UserFeed.java index e52210a1..18c1335b 100644 --- a/java/patterns-use-cases/src/main/java/my/example/eventtransactions/UserFeed.java +++ b/java/patterns-use-cases/src/main/java/my/example/eventtransactions/UserFeed.java @@ -4,7 +4,7 @@ import static my.example.eventtransactions.utils.Stubs.getPostStatus; import static my.example.eventtransactions.utils.Stubs.updateUserFeed; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.endpoint.Endpoint; @@ -21,20 +21,21 @@ public record SocialMediaPost(String content, String metadata) {} // Connect a handler to a Kafka topic. Restate manages the Kafka subscription and offsets. // Events are pushed in order to the Virtual Object (Kafka key = object key). @Handler - public void processPost(ObjectContext ctx, SocialMediaPost post) { - String userId = ctx.key(); + public void processPost(SocialMediaPost post) { + String userId = Restate.key(); // Durable side effects: Restate persists intermediate results and replays them on failure. - String postId = ctx.run(String.class, () -> createPost(userId, post)); + String postId = Restate.run("create post", String.class, () -> createPost(userId, post)); // No restrictions on the handler code: loops, sleeps, etc. - while (ctx.run(String.class, () -> getPostStatus(postId)).equals("PENDING")) { + while (Restate.run("get post status", String.class, () -> getPostStatus(postId)) + .equals("PENDING")) { // Delay processing until content moderation is complete (handler suspends when on FaaS). // This only blocks other posts for this user (Virtual Object), not for other users. - ctx.sleep(Duration.ofSeconds(5)); + Restate.sleep(Duration.ofSeconds(5)); } - ctx.run(() -> updateUserFeed(userId, postId)); + Restate.run("update feed", () -> updateUserFeed(userId, postId)); } public static void main(String[] args) { diff --git a/java/patterns-use-cases/src/main/java/my/example/parallelizework/FanOutWorker.java b/java/patterns-use-cases/src/main/java/my/example/parallelizework/FanOutWorker.java index a575c7c6..4b83e56c 100644 --- a/java/patterns-use-cases/src/main/java/my/example/parallelizework/FanOutWorker.java +++ b/java/patterns-use-cases/src/main/java/my/example/parallelizework/FanOutWorker.java @@ -2,8 +2,8 @@ import static my.example.parallelizework.utils.Utils.*; -import dev.restate.sdk.Context; import dev.restate.sdk.DurableFuture; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.endpoint.Endpoint; @@ -41,14 +41,15 @@ public class FanOutWorker { @Handler - public Result run(Context ctx, Task task) { + public Result run(Task task) { // Split the task in subtasks - var subTasks = ctx.run(new TypeRef<>() {}, () -> split(task)); + var subTasks = Restate.run("split", new TypeRef>() {}, () -> split(task)); // Fan out the subtasks - run them in parallel List> resultFutures = new ArrayList<>(); for (SubTask subTask : subTasks) { - resultFutures.add(FanOutWorkerClient.fromContext(ctx).runSubtask(subTask)); + resultFutures.add( + Restate.serviceHandle(FanOutWorker.class).call(FanOutWorker::runSubtask, subTask)); } DurableFuture.all(resultFutures).await(); @@ -60,10 +61,10 @@ public Result run(Context ctx, Task task) { // Can also run on FaaS @Handler - public SubTaskResult runSubtask(Context ctx, SubTask subTask) { + public SubTaskResult runSubtask(SubTask subTask) { // Processing logic goes here ... // Can be moved to a separate service to scale independently - return executeSubtask(ctx, subTask); + return executeSubtask(subTask); } public static void main(String[] args) { diff --git a/java/patterns-use-cases/src/main/java/my/example/parallelizework/utils/Utils.java b/java/patterns-use-cases/src/main/java/my/example/parallelizework/utils/Utils.java index 9ae43608..b4f27324 100644 --- a/java/patterns-use-cases/src/main/java/my/example/parallelizework/utils/Utils.java +++ b/java/patterns-use-cases/src/main/java/my/example/parallelizework/utils/Utils.java @@ -1,6 +1,6 @@ package my.example.parallelizework.utils; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import java.time.Duration; import java.util.Arrays; import java.util.List; @@ -17,11 +17,11 @@ public static List split(Task task) { return Arrays.stream(task.description().split(",")).map(SubTask::new).toList(); } - public static SubTaskResult executeSubtask(Context ctx, SubTask subtask) { + public static SubTaskResult executeSubtask(SubTask subtask) { // Execute subtask logger.info("Started executing subtask: {}", subtask.description()); // Sleep for a random amount between 0 and 5 seconds - ctx.sleep(Duration.ofSeconds(ctx.random().nextInt(0, 10))); + Restate.sleep(Duration.ofSeconds(Restate.random().nextInt(0, 10))); logger.info("Execution subtask finished: {}", subtask.description()); return new SubTaskResult(subtask.description() + ": DONE"); } diff --git a/java/patterns-use-cases/src/main/java/my/example/queue/AsyncTaskWorker.java b/java/patterns-use-cases/src/main/java/my/example/queue/AsyncTaskWorker.java index bcf3ff1c..5997c2d5 100644 --- a/java/patterns-use-cases/src/main/java/my/example/queue/AsyncTaskWorker.java +++ b/java/patterns-use-cases/src/main/java/my/example/queue/AsyncTaskWorker.java @@ -1,6 +1,5 @@ package my.example.queue; -import dev.restate.sdk.Context; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.endpoint.Endpoint; @@ -12,7 +11,7 @@ public class AsyncTaskWorker { public record TaskOpts(String key, String taskName, String payload) {} @Handler - public String runTask(Context ctx, TaskOpts params) { + public String runTask(TaskOpts params) { return someHeavyWork(params); } diff --git a/java/patterns-use-cases/src/main/java/my/example/queue/TaskSubmitter.java b/java/patterns-use-cases/src/main/java/my/example/queue/TaskSubmitter.java index 0f727f49..18629fc4 100644 --- a/java/patterns-use-cases/src/main/java/my/example/queue/TaskSubmitter.java +++ b/java/patterns-use-cases/src/main/java/my/example/queue/TaskSubmitter.java @@ -1,6 +1,7 @@ package my.example.queue; import dev.restate.client.Client; +import dev.restate.common.InvocationOptions; /* * Restate is as a sophisticated task queue, with extra features like: @@ -22,15 +23,16 @@ public void submitAndAwaitTasks(AsyncTaskWorker.TaskOpts taskOpts) { // submit the task; similar to publishing a message to a queue // Restate ensures the task is executed exactly once var handle = - AsyncTaskWorkerClient.fromClient(restateClient) + restateClient + .serviceHandle(AsyncTaskWorker.class) // optionally add a delay to execute the task later - .send() - .runTask( + .send( + AsyncTaskWorker::runTask, taskOpts, // optionally add a delay to execute the task later // Duration.ofDays(1), // use a stable uuid as an idempotency key; Restate deduplicates for us - opts -> opts.idempotencyKey("dQw4w9WgXcQ")); + InvocationOptions.idempotencyKey("dQw4w9WgXcQ")); // ... do other things while the task is being processed ... diff --git a/java/patterns-use-cases/src/main/java/my/example/sagas/BookingWorkflow.java b/java/patterns-use-cases/src/main/java/my/example/sagas/BookingWorkflow.java index b6b6e22a..1d03950b 100644 --- a/java/patterns-use-cases/src/main/java/my/example/sagas/BookingWorkflow.java +++ b/java/patterns-use-cases/src/main/java/my/example/sagas/BookingWorkflow.java @@ -1,6 +1,6 @@ package my.example.sagas; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.common.TerminalException; @@ -48,20 +48,23 @@ public record BookingRequest( String customerId, FlightRequest flight, CarRequest car, HotelRequest hotel) {} @Handler - public void run(Context ctx, BookingRequest req) throws TerminalException { + public void run(BookingRequest req) throws TerminalException { // Create a list of undo actions List compensations = new ArrayList<>(); try { // For each action, we register a compensation that will be executed on failures - compensations.add(() -> ctx.run("Cancel flight", () -> FlightClient.cancel(req.customerId))); - ctx.run("Book flight", () -> FlightClient.book(req.customerId, req.flight())); + compensations.add( + () -> Restate.run("Cancel flight", () -> FlightClient.cancel(req.customerId))); + Restate.run("Book flight", () -> FlightClient.book(req.customerId, req.flight())); - compensations.add(() -> ctx.run("Cancel car", () -> CarRentalClient.cancel(req.customerId))); - ctx.run("Book car", () -> CarRentalClient.book(req.customerId, req.car())); + compensations.add( + () -> Restate.run("Cancel car", () -> CarRentalClient.cancel(req.customerId))); + Restate.run("Book car", () -> CarRentalClient.book(req.customerId, req.car())); - compensations.add(() -> ctx.run("Cancel hotel", () -> HotelClient.cancel(req.customerId))); - ctx.run("Book hotel", () -> HotelClient.book(req.customerId, req.hotel())); + compensations.add( + () -> Restate.run("Cancel hotel", () -> HotelClient.cancel(req.customerId))); + Restate.run("Book hotel", () -> HotelClient.book(req.customerId, req.hotel())); } // Terminal exceptions are not retried by Restate. We undo previous actions and fail the // workflow. diff --git a/java/patterns-use-cases/src/main/java/my/example/schedulingtasks/PaymentTracker.java b/java/patterns-use-cases/src/main/java/my/example/schedulingtasks/PaymentTracker.java index 6011d5ba..ba93ef42 100644 --- a/java/patterns-use-cases/src/main/java/my/example/schedulingtasks/PaymentTracker.java +++ b/java/patterns-use-cases/src/main/java/my/example/schedulingtasks/PaymentTracker.java @@ -3,7 +3,7 @@ import static my.example.schedulingtasks.utils.Utils.escalateToHuman; import static my.example.schedulingtasks.utils.Utils.sendReminderEmail; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; @@ -21,29 +21,30 @@ public class PaymentTracker { // Stripe sends us webhook events for invoice payment attempts @Handler - public void onPaymentSuccess(ObjectContext ctx, StripeEvent event) { - ctx.set(PAID, true); + public void onPaymentSuccess(StripeEvent event) { + Restate.state().set(PAID, true); } @Handler - public void onPaymentFailure(ObjectContext ctx, StripeEvent event) { + public void onPaymentFailure(StripeEvent event) { + var state = Restate.state(); + // Already paid, no need to send reminders - if (ctx.get(PAID).orElse(false)) { + if (state.get(PAID).orElse(false)) { return; } - int remindersCount = ctx.get(REMINDER_COUNT).orElse(0); + int remindersCount = state.get(REMINDER_COUNT).orElse(0); if (remindersCount < 3) { - ctx.set(REMINDER_COUNT, remindersCount + 1); - ctx.run(() -> sendReminderEmail(event)); + state.set(REMINDER_COUNT, remindersCount + 1); + Restate.run("send reminder", () -> sendReminderEmail(event)); // Schedule next reminder via a delayed self call - PaymentTrackerClient.fromContext(ctx, ctx.key()) - .send() - .onPaymentFailure(event, Duration.ofDays(1)); + Restate.virtualObjectHandle(PaymentTracker.class, Restate.key()) + .send(PaymentTracker::onPaymentFailure, event, Duration.ofDays(1)); } else { // After three reminders, escalate to support team - ctx.run(() -> escalateToHuman(event)); + Restate.run("escalate", () -> escalateToHuman(event)); } } diff --git a/java/patterns-use-cases/src/main/java/my/example/signalspayments/PaymentService.java b/java/patterns-use-cases/src/main/java/my/example/signalspayments/PaymentService.java index 42064240..3a1d3fc2 100644 --- a/java/patterns-use-cases/src/main/java/my/example/signalspayments/PaymentService.java +++ b/java/patterns-use-cases/src/main/java/my/example/signalspayments/PaymentService.java @@ -4,7 +4,7 @@ import com.stripe.model.PaymentIntent; import com.stripe.net.ApiResource; import dev.restate.sdk.Awakeable; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Accept; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Raw; @@ -45,18 +45,18 @@ public class PaymentService { public record PaymentRequest(Long amount, String paymentMethodId, boolean delayedStatus) {} @Handler - public void processPayment(Context ctx, PaymentRequest request) { + public void processPayment(PaymentRequest request) { PaymentUtils.verifyPaymentRequest(request); // Generate a deterministic idempotency key - String idempotencyKey = ctx.random().nextUUID().toString(); + String idempotencyKey = Restate.random().nextUUID().toString(); // Initiate a listener for external calls for potential webhook callbacks - Awakeable webhookPromise = ctx.awakeable(paymentIntentSerde); + Awakeable webhookPromise = Restate.awakeable(paymentIntentSerde); // Make a synchronous call to the payment service PaymentIntent paymentIntent = - ctx.run( + Restate.run( "Stripe call", paymentIntentSerde, () -> { @@ -91,10 +91,10 @@ public void processPayment(Context ctx, PaymentRequest request) { @Handler public boolean processWebhook( - Context ctx, // The raw request is the webhook call from Stripe that we will verify in the handler @Accept("*/*") @Raw byte[] request) { - Event event = stripe.parseWebhookCall(request, ctx.request().headers().get("stripe-signature")); + Event event = + stripe.parseWebhookCall(request, Restate.request().headers().get("stripe-signature")); if (!PaymentUtils.isPaymentIntent(event)) { logger.info("Unhandled event type: {}", event.getType()); @@ -111,7 +111,7 @@ public boolean processWebhook( 400, "Missing callback property: " + PaymentUtils.RESTATE_CALLBACK_ID); } - ctx.awakeableHandle(webhookPromise).resolve(paymentIntentSerde, paymentIntent); + Restate.awakeableHandle(webhookPromise).resolve(paymentIntentSerde, paymentIntent); return true; } diff --git a/java/patterns-use-cases/src/main/java/my/example/statefulactors/MachineOperator.java b/java/patterns-use-cases/src/main/java/my/example/statefulactors/MachineOperator.java index fbe75a86..7c2283dc 100644 --- a/java/patterns-use-cases/src/main/java/my/example/statefulactors/MachineOperator.java +++ b/java/patterns-use-cases/src/main/java/my/example/statefulactors/MachineOperator.java @@ -3,7 +3,7 @@ import static my.example.statefulactors.utils.MachineOperations.bringUpMachine; import static my.example.statefulactors.utils.MachineOperations.tearDownMachine; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; @@ -31,34 +31,34 @@ enum Status { private static final StateKey STATUS = StateKey.of("state", Status.class); @Handler - public String setUp(ObjectContext ctx) { - String machineId = ctx.key(); + public String setUp() { + String machineId = Restate.key(); // Ignore duplicate calls to start - var status = ctx.get(STATUS).orElse(Status.DOWN); + var status = Restate.state().get(STATUS).orElse(Status.DOWN); if (status.equals(Status.UP)) { return machineId + " is already running"; } // Bringing up a machine is a slow process that frequently crashes - bringUpMachine(ctx, machineId); - ctx.set(STATUS, Status.UP); + bringUpMachine(machineId); + Restate.state().set(STATUS, Status.UP); return machineId + " is now running"; } @Handler - public String tearDown(ObjectContext ctx) { - String machineId = ctx.key(); + public String tearDown() { + String machineId = Restate.key(); - var status = ctx.get(STATUS).orElse(Status.DOWN); + var status = Restate.state().get(STATUS).orElse(Status.DOWN); if (!status.equals(Status.UP)) { return machineId + " is not up, cannot tear down"; } // Tearing down a machine is a slow process that frequently crashes - tearDownMachine(ctx, machineId); - ctx.set(STATUS, Status.DOWN); + tearDownMachine(machineId); + Restate.state().set(STATUS, Status.DOWN); return machineId + " is now down"; } diff --git a/java/patterns-use-cases/src/main/java/my/example/statefulactors/utils/MachineOperations.java b/java/patterns-use-cases/src/main/java/my/example/statefulactors/utils/MachineOperations.java index 81c3fafe..d5df8635 100644 --- a/java/patterns-use-cases/src/main/java/my/example/statefulactors/utils/MachineOperations.java +++ b/java/patterns-use-cases/src/main/java/my/example/statefulactors/utils/MachineOperations.java @@ -1,6 +1,6 @@ package my.example.statefulactors.utils; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import java.time.Duration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -22,21 +22,21 @@ public static void maybeCrash(double probability) { } } - public static void bringUpMachine(Context ctx, String machineId) { + public static void bringUpMachine(String machineId) { logger.info("{} beginning transition to UP", machineId); // Some long fragile process maybeCrash(0.4); - ctx.sleep(Duration.ofMillis(5000)); + Restate.sleep(Duration.ofMillis(5000)); logger.info("{} is now running", machineId); } - public static void tearDownMachine(Context ctx, String machineId) { + public static void tearDownMachine(String machineId) { logger.info("{} beginning transition to down", machineId); // Some long fragile process maybeCrash(0.4); - ctx.sleep(Duration.ofMillis(5000)); + Restate.sleep(Duration.ofMillis(5000)); logger.info("{} is now down", machineId); } } diff --git a/java/patterns-use-cases/src/main/java/my/example/statemachinepayments/PaymentProcessor.java b/java/patterns-use-cases/src/main/java/my/example/statemachinepayments/PaymentProcessor.java index 663ececd..41736cf2 100644 --- a/java/patterns-use-cases/src/main/java/my/example/statemachinepayments/PaymentProcessor.java +++ b/java/patterns-use-cases/src/main/java/my/example/statemachinepayments/PaymentProcessor.java @@ -2,12 +2,12 @@ import static my.example.statemachinepayments.types.PaymentStatus.*; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; import java.time.Duration; -import my.example.statemachinepayments.accounts.AccountClient; +import my.example.statemachinepayments.accounts.Account; import my.example.statemachinepayments.types.*; /* @@ -33,9 +33,9 @@ public class PaymentProcessor { private static final Duration EXPIRY_TIMEOUT = Duration.ofDays(1); @Handler - public Result makePayment(ObjectContext ctx, Payment payment) { - final String paymentId = ctx.key(); - final PaymentStatus status = ctx.get(STATUS).orElse(NEW); + public Result makePayment(Payment payment) { + final String paymentId = Restate.key(); + final PaymentStatus status = Restate.state().get(STATUS).orElse(NEW); if (status == CANCELLED) { return new Result(false, "Payment already cancelled"); @@ -46,50 +46,50 @@ public Result makePayment(ObjectContext ctx, Payment payment) { // Charge the target account Result paymentResult = - AccountClient.fromContext(ctx, payment.getAccountId()) - .withdraw(payment.getAmountCents()) - .await(); + Restate.virtualObject(Account.class, payment.getAccountId()) + .withdraw(payment.getAmountCents()); // Remember only on success, so that on failure (when we didn't charge) the external // caller may retry this (with the same payment-id), for the sake of this example if (paymentResult.isSuccess()) { - ctx.set(STATUS, COMPLETED_SUCCESSFULLY); - ctx.set(PAYMENT, payment); - PaymentProcessorClient.fromContext(ctx, paymentId).send().expire(EXPIRY_TIMEOUT); + Restate.state().set(STATUS, COMPLETED_SUCCESSFULLY); + Restate.state().set(PAYMENT, payment); + Restate.virtualObjectHandle(PaymentProcessor.class, paymentId) + .send(PaymentProcessor::expire, EXPIRY_TIMEOUT); } return paymentResult; } @Handler - public void cancelPayment(ObjectContext ctx) { - PaymentStatus status = ctx.get(STATUS).orElse(NEW); + public void cancelPayment() { + PaymentStatus status = Restate.state().get(STATUS).orElse(NEW); switch (status) { case NEW -> { // not seen this payment-id before, mark as canceled, in case the cancellation // overtook the actual payment request (on the external caller's side) - ctx.set(STATUS, CANCELLED); - PaymentProcessorClient.fromContext(ctx, ctx.key()).send().expire(EXPIRY_TIMEOUT); + Restate.state().set(STATUS, CANCELLED); + Restate.virtualObjectHandle(PaymentProcessor.class, Restate.key()) + .send(PaymentProcessor::expire, EXPIRY_TIMEOUT); } case CANCELLED -> {} case COMPLETED_SUCCESSFULLY -> { // remember this as cancelled - ctx.set(STATUS, CANCELLED); + Restate.state().set(STATUS, CANCELLED); // undo the payment - Payment payment = ctx.get(PAYMENT).get(); - AccountClient.fromContext(ctx, payment.getAccountId()) - .send() - .deposit(payment.getAmountCents()); + Payment payment = Restate.state().get(PAYMENT).get(); + Restate.virtualObjectHandle(Account.class, payment.getAccountId()) + .send(Account::deposit, payment.getAmountCents()); } } } @Handler - public void expire(ObjectContext ctx) { - ctx.clearAll(); + public void expire() { + Restate.state().clearAll(); } } diff --git a/java/patterns-use-cases/src/main/java/my/example/statemachinepayments/accounts/Account.java b/java/patterns-use-cases/src/main/java/my/example/statemachinepayments/accounts/Account.java index b788656e..2ba322b0 100644 --- a/java/patterns-use-cases/src/main/java/my/example/statemachinepayments/accounts/Account.java +++ b/java/patterns-use-cases/src/main/java/my/example/statemachinepayments/accounts/Account.java @@ -1,6 +1,6 @@ package my.example.statemachinepayments.accounts; -import dev.restate.sdk.ObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; @@ -18,27 +18,27 @@ public class Account { private static final StateKey BALANCE = StateKey.of("balance", Long.TYPE); @Handler - public void deposit(ObjectContext ctx, Long amountCents) { + public void deposit(Long amountCents) { if (amountCents <= 0) { throw new TerminalException("Amount must be greater than 0"); } - long balanceCents = ctx.get(BALANCE).orElse(initializeRandomAmount()); - ctx.set(BALANCE, balanceCents + amountCents); + long balanceCents = Restate.state().get(BALANCE).orElse(initializeRandomAmount()); + Restate.state().set(BALANCE, balanceCents + amountCents); } @Handler - public Result withdraw(ObjectContext ctx, Long amountCents) { + public Result withdraw(Long amountCents) { if (amountCents <= 0) { throw new TerminalException("Amount must be greater than 0"); } - long balanceCents = ctx.get(BALANCE).orElse(initializeRandomAmount()); + long balanceCents = Restate.state().get(BALANCE).orElse(initializeRandomAmount()); if (balanceCents < amountCents) { return new Result(false, "Insufficient funds: " + balanceCents + " cents"); } - ctx.set(BALANCE, balanceCents - amountCents); + Restate.state().set(BALANCE, balanceCents - amountCents); return new Result(true, "Withdrawal successful"); } diff --git a/java/patterns-use-cases/src/main/java/my/example/syncasync/DataUploadService.java b/java/patterns-use-cases/src/main/java/my/example/syncasync/DataUploadService.java index 269d21c5..fda3fc9b 100644 --- a/java/patterns-use-cases/src/main/java/my/example/syncasync/DataUploadService.java +++ b/java/patterns-use-cases/src/main/java/my/example/syncasync/DataUploadService.java @@ -3,8 +3,7 @@ import static my.example.syncasync.utils.DataOperations.createS3Bucket; import static my.example.syncasync.utils.DataOperations.uploadData; -import dev.restate.sdk.SharedWorkflowContext; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.common.DurablePromiseKey; @@ -19,19 +18,19 @@ public class DataUploadService { DurablePromiseKey.of("url", String.class); @Workflow - public String run(WorkflowContext ctx) { - String url = ctx.run(String.class, () -> createS3Bucket()); - ctx.run(() -> uploadData(url)); + public String run() { + String url = Restate.run("create bucket", String.class, () -> createS3Bucket()); + Restate.run("upload", () -> uploadData(url)); - ctx.promiseHandle(URL_PROMISE).resolve(url); + Restate.promiseHandle(URL_PROMISE).resolve(url); return url; } @Shared - public void resultAsEmail(SharedWorkflowContext ctx, String email) { - String url = ctx.promise(URL_PROMISE).future().await(); - ctx.run(() -> EmailClient.send(url, email)); + public void resultAsEmail(String email) { + String url = Restate.promise(URL_PROMISE).future().await(); + Restate.run("send email", () -> EmailClient.send(url, email)); } public static void main(String[] args) { diff --git a/java/patterns-use-cases/src/main/java/my/example/syncasync/UploadClient.java b/java/patterns-use-cases/src/main/java/my/example/syncasync/UploadClient.java index 42a8aa9b..f270a17b 100644 --- a/java/patterns-use-cases/src/main/java/my/example/syncasync/UploadClient.java +++ b/java/patterns-use-cases/src/main/java/my/example/syncasync/UploadClient.java @@ -20,16 +20,16 @@ public void uploadData(String userId, String email) throws Exception { // Submit the workflow Client restateClient = Client.connect(RESTATE_URL); - var uploadClient = DataUploadServiceClient.fromClient(restateClient, userId); - uploadClient.submit(); + var handle = + restateClient.workflowHandle(DataUploadService.class, userId).send(DataUploadService::run); String url; try { // Wait for the workflow to complete or timeout - url = uploadClient.workflowHandle().attachAsync().get(5, TimeUnit.SECONDS).response(); + url = handle.attachAsync().get(5, TimeUnit.SECONDS).response(); } catch (TimeoutException e) { logger.info("Slow upload... Mail the link later"); - uploadClient.resultAsEmail(email); + restateClient.workflow(DataUploadService.class, userId).resultAsEmail(email); return; } diff --git a/java/templates/java-gradle/README.md b/java/templates/java-gradle/README.md index d6bef3b2..444cff37 100644 --- a/java/templates/java-gradle/README.md +++ b/java/templates/java-gradle/README.md @@ -10,9 +10,6 @@ To run: ./gradlew run ``` -Restate SDK uses annotation processing to generate client classes. -When modifying the annotated services in Intellij, it is suggested to run **CTRL + F9** to re-generate the client classes. - ## Using AI coding tools If you use Claude Code or Codex, then the Restate plugin will automatically be installed. For Cursor, consult the [skills repo README](https://github.com/restatedev/skills). diff --git a/java/templates/java-gradle/build.gradle.kts b/java/templates/java-gradle/build.gradle.kts index e3b3a470..a0c12c12 100644 --- a/java/templates/java-gradle/build.gradle.kts +++ b/java/templates/java-gradle/build.gradle.kts @@ -7,11 +7,9 @@ repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-java-http:$restateVersion") @@ -26,15 +24,19 @@ dependencies { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(25)) } } // Set main class application { mainClass.set("my.example.Greeter") + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } tasks.named("test") { useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } diff --git a/java/templates/java-gradle/gradle/wrapper/gradle-wrapper.jar b/java/templates/java-gradle/gradle/wrapper/gradle-wrapper.jar index 1b33c55b..b1b8ef56 100644 Binary files a/java/templates/java-gradle/gradle/wrapper/gradle-wrapper.jar and b/java/templates/java-gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/templates/java-gradle/gradle/wrapper/gradle-wrapper.properties b/java/templates/java-gradle/gradle/wrapper/gradle-wrapper.properties index 37f78a6a..a9db1155 100644 --- a/java/templates/java-gradle/gradle/wrapper/gradle-wrapper.properties +++ b/java/templates/java-gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/templates/java-gradle/gradlew b/java/templates/java-gradle/gradlew index 23d15a93..249efbb0 100755 --- a/java/templates/java-gradle/gradlew +++ b/java/templates/java-gradle/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/java/templates/java-gradle/gradlew.bat b/java/templates/java-gradle/gradlew.bat index 5eed7ee8..8508ef68 100644 --- a/java/templates/java-gradle/gradlew.bat +++ b/java/templates/java-gradle/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH= -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/templates/java-gradle/src/main/java/my/example/Greeter.java b/java/templates/java-gradle/src/main/java/my/example/Greeter.java index fcb46da0..663f19e3 100644 --- a/java/templates/java-gradle/src/main/java/my/example/Greeter.java +++ b/java/templates/java-gradle/src/main/java/my/example/Greeter.java @@ -1,6 +1,6 @@ package my.example; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.endpoint.Endpoint; @@ -18,12 +18,12 @@ public record Greeting(String name) {} public record GreetingResponse(String message) {} @Handler - public GreetingResponse greet(Context ctx, Greeting req) { + public GreetingResponse greet(Greeting req) { // Durably execute a set of steps; resilient against failures - String greetingId = ctx.random().nextUUID().toString(); - ctx.run("Notification", () -> sendNotification(greetingId, req.name)); - ctx.sleep(Duration.ofSeconds(1)); - ctx.run("Reminder", () -> sendReminder(greetingId, req.name)); + String greetingId = Restate.random().nextUUID().toString(); + Restate.run("Notification", () -> sendNotification(greetingId, req.name)); + Restate.sleep(Duration.ofSeconds(1)); + Restate.run("Reminder", () -> sendReminder(greetingId, req.name)); // Respond to caller return new GreetingResponse("You said hi to " + req.name + "!"); diff --git a/java/templates/java-gradle/src/test/java/my/example/GreeterTest.java b/java/templates/java-gradle/src/test/java/my/example/GreeterTest.java index e72e54b7..bc473ca7 100644 --- a/java/templates/java-gradle/src/test/java/my/example/GreeterTest.java +++ b/java/templates/java-gradle/src/test/java/my/example/GreeterTest.java @@ -28,7 +28,7 @@ class GreeterTest { @Test @Timeout(value = 10) void testGreet(@RestateClient Client ingressClient) { - var client = GreeterClient.fromClient(ingressClient); + var client = ingressClient.service(Greeter.class); GreetingResponse response = client.greet(new Greeting("Francesco")); assertEquals(response.message(), "You said hi to Francesco!"); diff --git a/java/templates/java-maven-quarkus/.mvn/wrapper/MavenWrapperDownloader.java b/java/templates/java-maven-quarkus/.mvn/wrapper/MavenWrapperDownloader.java index fe7d037d..7e9c1e3c 100644 --- a/java/templates/java-maven-quarkus/.mvn/wrapper/MavenWrapperDownloader.java +++ b/java/templates/java-maven-quarkus/.mvn/wrapper/MavenWrapperDownloader.java @@ -30,7 +30,7 @@ import java.util.concurrent.ThreadLocalRandom; public final class MavenWrapperDownloader { - private static final String WRAPPER_VERSION = "3.3.2"; + private static final String WRAPPER_VERSION = "3.3.4"; private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); @@ -45,8 +45,11 @@ public static void main(String[] args) { try { log(" - Downloader started"); final URL wrapperUrl = URI.create(args[0]).toURL(); - final String jarPath = args[1].replace("..", ""); // Sanitize path - final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize(); + final Path baseDir = Paths.get(".").toAbsolutePath().normalize(); + final Path wrapperJarPath = baseDir.resolve(args[1]).normalize(); + if (!wrapperJarPath.startsWith(baseDir)) { + throw new IOException("Invalid path: outside of allowed directory"); + } downloadFileFromURL(wrapperUrl, wrapperJarPath); log("Done"); } catch (IOException e) { diff --git a/java/templates/java-maven-quarkus/.mvn/wrapper/maven-wrapper.properties b/java/templates/java-maven-quarkus/.mvn/wrapper/maven-wrapper.properties index 72df6c61..599ac34a 100644 --- a/java/templates/java-maven-quarkus/.mvn/wrapper/maven-wrapper.properties +++ b/java/templates/java-maven-quarkus/.mvn/wrapper/maven-wrapper.properties @@ -1,20 +1,4 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=source -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar \ No newline at end of file +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar diff --git a/java/templates/java-maven-quarkus/mvnw b/java/templates/java-maven-quarkus/mvnw index 5e9618ca..1ddd97b9 100755 --- a/java/templates/java-maven-quarkus/mvnw +++ b/java/templates/java-maven-quarkus/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Required ENV vars: # ------------------ @@ -201,6 +201,14 @@ MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} export MAVEN_PROJECTBASEDIR log "$MAVEN_PROJECTBASEDIR" +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. @@ -212,15 +220,13 @@ else log "Couldn't find $wrapperJarPath, downloading it ..." if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" fi while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') case "$key" in wrapperUrl) - wrapperUrl="$safeValue" + wrapperUrl=$(trim "${value-}") break ;; esac @@ -235,17 +241,17 @@ else log "Found wget ... using wget" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl >/dev/null; then log "Found curl ... using curl" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi else log "Falling back to using Java to download" @@ -276,7 +282,7 @@ fi wrapperSha256Sum="" while IFS="=" read -r key value; do case "$key" in wrapperSha256Sum) - wrapperSha256Sum=$value + wrapperSha256Sum=$(trim "${value-}") break ;; esac @@ -284,7 +290,7 @@ done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" if [ -n "$wrapperSha256Sum" ]; then wrapperSha256Result=false if command -v sha256sum >/dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then wrapperSha256Result=true fi elif command -v shasum >/dev/null; then diff --git a/java/templates/java-maven-quarkus/mvnw.cmd b/java/templates/java-maven-quarkus/mvnw.cmd index 4136715f..c453424c 100644 --- a/java/templates/java-maven-quarkus/mvnw.cmd +++ b/java/templates/java-maven-quarkus/mvnw.cmd @@ -1,206 +1,206 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. >&2 -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. >&2 -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. >&2 -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. >&2 -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B -) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. >&2 +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. >&2 +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. >&2 +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. >&2 +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/java/templates/java-maven-quarkus/pom.xml b/java/templates/java-maven-quarkus/pom.xml index 284bd8b2..d4de689a 100644 --- a/java/templates/java-maven-quarkus/pom.xml +++ b/java/templates/java-maven-quarkus/pom.xml @@ -7,7 +7,7 @@ 3.13.0 - 17 + 25 UTF-8 UTF-8 quarkus-bom @@ -15,9 +15,10 @@ 3.15.1 true 3.3.1 - 2.7.0 + 2.9.0 + @@ -64,11 +65,31 @@ + + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + select-jdk-toolchain + + + + + [25,) + + ${quarkus.platform.group-id} quarkus-maven-plugin ${quarkus.platform.version} true + + + --enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow + @@ -85,13 +106,6 @@ ${compiler-plugin.version} true - - - dev.restate - sdk-api-gen - ${restate.version} - - diff --git a/java/templates/java-maven-quarkus/src/main/java/org/acme/Greeter.java b/java/templates/java-maven-quarkus/src/main/java/org/acme/Greeter.java index 31ddc6cf..7b60ee68 100644 --- a/java/templates/java-maven-quarkus/src/main/java/org/acme/Greeter.java +++ b/java/templates/java-maven-quarkus/src/main/java/org/acme/Greeter.java @@ -1,6 +1,6 @@ package org.acme; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import jakarta.enterprise.context.ApplicationScoped; @@ -21,12 +21,12 @@ public record Greeting(String name) {} public record GreetingResponse(String message) {} @Handler - public GreetingResponse greet(Context ctx, Greeting req) { + public GreetingResponse greet(Greeting req) { // Durably execute a set of steps; resilient against failures - String greetingId = ctx.random().nextUUID().toString(); - ctx.run("Notification", () -> sendNotification(greetingId, req.name)); - ctx.sleep(Duration.ofSeconds(1)); - ctx.run("Reminder", () -> sendReminder(greetingId, req.name)); + String greetingId = Restate.random().nextUUID().toString(); + Restate.run("Notification", () -> sendNotification(greetingId, req.name)); + Restate.sleep(Duration.ofSeconds(1)); + Restate.run("Reminder", () -> sendReminder(greetingId, req.name)); // Respond to caller return new GreetingResponse("You said hi to " + req.name + "!"); diff --git a/java/templates/java-maven-spring-boot/.mvn/wrapper/maven-wrapper.properties b/java/templates/java-maven-spring-boot/.mvn/wrapper/maven-wrapper.properties index d58dfb70..216df058 100644 --- a/java/templates/java-maven-spring-boot/.mvn/wrapper/maven-wrapper.properties +++ b/java/templates/java-maven-spring-boot/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/java/templates/java-maven-spring-boot/README.md b/java/templates/java-maven-spring-boot/README.md index 1426e395..e512d5db 100644 --- a/java/templates/java-maven-spring-boot/README.md +++ b/java/templates/java-maven-spring-boot/README.md @@ -12,9 +12,6 @@ To start the service, simply run: $ mvn compile spring-boot:run ``` -Restate SDK uses annotation processing to generate client classes. -When modifying the annotated services in Intellij, it is suggested to run **CTRL + F9** to re-generate the client classes. - ## Using AI coding tools If you use Claude Code or Codex, then the Restate plugin will automatically be installed. For Cursor, consult the [skills repo README](https://github.com/restatedev/skills). diff --git a/java/templates/java-maven-spring-boot/mvnw b/java/templates/java-maven-spring-boot/mvnw index 19529ddf..bd8896bf 100755 --- a/java/templates/java-maven-spring-boot/mvnw +++ b/java/templates/java-maven-spring-boot/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Optional ENV vars # ----------------- @@ -105,14 +105,17 @@ trim() { printf "%s" "${1}" | tr -d '[:space:]' } +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) @@ -130,7 +133,7 @@ maven-mvnd-*bin.*) distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME @@ -227,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then @@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" diff --git a/java/templates/java-maven-spring-boot/mvnw.cmd b/java/templates/java-maven-spring-boot/mvnw.cmd index 249bdf38..92450f93 100644 --- a/java/templates/java-maven-spring-boot/mvnw.cmd +++ b/java/templates/java-maven-spring-boot/mvnw.cmd @@ -19,7 +19,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @@ -40,7 +40,7 @@ @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> @@ -73,16 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" + +$MAVEN_M2_PATH = "$HOME/.m2" if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" } -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { @@ -134,7 +148,33 @@ if ($distributionSha256Sum) { # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { diff --git a/java/templates/java-maven-spring-boot/pom.xml b/java/templates/java-maven-spring-boot/pom.xml index d5feb111..9c0c44a3 100644 --- a/java/templates/java-maven-spring-boot/pom.xml +++ b/java/templates/java-maven-spring-boot/pom.xml @@ -27,9 +27,11 @@ - 17 - 2.7.0 + 25 + 2.9.0 + + org.springframework.boot @@ -55,23 +57,37 @@ + + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + select-jdk-toolchain + + + + + [25,) + + org.springframework.boot spring-boot-maven-plugin + + + --enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow + + + + maven-surefire-plugin + + + --enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow + - - maven-compiler-plugin - - true - - - dev.restate - sdk-api-gen - ${restate.version} - - - - diff --git a/java/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java b/java/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java index bbc24a06..63de5f5c 100644 --- a/java/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java +++ b/java/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java @@ -1,8 +1,9 @@ package com.example.restatestarter; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.springboot.RestateService; +import dev.restate.sdk.annotation.Service; +import dev.restate.sdk.springboot.RestateComponent; import org.springframework.beans.factory.annotation.Value; import java.time.Duration; @@ -10,7 +11,8 @@ import static com.example.restatestarter.Utils.sendNotification; import static com.example.restatestarter.Utils.sendReminder; -@RestateService +@RestateComponent +@Service public class Greeter { @Value("${greetingPrefix}") @@ -20,12 +22,12 @@ public record Greeting(String name) {} public record GreetingResponse(String message) {} @Handler - public GreetingResponse greet(Context ctx, Greeting req) { + public GreetingResponse greet(Greeting req) { // Durably execute a set of steps; resilient against failures - String greetingId = ctx.random().nextUUID().toString(); - ctx.run("Notification", () -> sendNotification(greetingId, req.name)); - ctx.sleep(Duration.ofSeconds(1)); - ctx.run("Reminder", () -> sendReminder(greetingId, req.name)); + String greetingId = Restate.random().nextUUID().toString(); + Restate.run("Notification", () -> sendNotification(greetingId, req.name)); + Restate.sleep(Duration.ofSeconds(1)); + Restate.run("Reminder", () -> sendReminder(greetingId, req.name)); // Respond to caller return new GreetingResponse("You said " + greetingPrefix + " to " + req.name + "!"); diff --git a/java/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/HelloController.java b/java/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/HelloController.java index 9bfca0ff..45588de3 100644 --- a/java/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/HelloController.java +++ b/java/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/HelloController.java @@ -16,7 +16,7 @@ public HelloController(Client restateClient) { @GetMapping("/") public String index() { - return GreeterClient.fromClient(restateClient).greet(new Greeting("Alice")) + " from Spring Boot!"; + return restateClient.service(Greeter.class).greet(new Greeting("Alice")) + " from Spring Boot!"; } } \ No newline at end of file diff --git a/java/templates/java-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java b/java/templates/java-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java index 0a3408f1..54696d99 100644 --- a/java/templates/java-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java +++ b/java/templates/java-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java @@ -29,7 +29,7 @@ public class GreeterTest { @Test @Timeout(value = 10) void greet(@RestateClient Client ingressClient) { - var client = GreeterClient.fromClient(ingressClient); + var client = ingressClient.service(Greeter.class); var response = client.greet(new Greeting("Francesco")); assertThat(response.message()).isEqualTo("You said ciao to Francesco!"); diff --git a/java/templates/java-maven/.mvn/wrapper/maven-wrapper.properties b/java/templates/java-maven/.mvn/wrapper/maven-wrapper.properties index d58dfb70..216df058 100644 --- a/java/templates/java-maven/.mvn/wrapper/maven-wrapper.properties +++ b/java/templates/java-maven/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/java/templates/java-maven/README.md b/java/templates/java-maven/README.md index 714e2837..370b8abc 100644 --- a/java/templates/java-maven/README.md +++ b/java/templates/java-maven/README.md @@ -7,12 +7,9 @@ Have a look at the [Java Quickstart guide](https://docs.restate.dev/get_started/ To run: ```shell -mvn compile exec:java +mvn compile exec:exec ``` -Restate SDK uses annotation processing to generate client classes. -When modifying the annotated services in Intellij, it is suggested to run **CTRL + F9** to re-generate the client classes. - ## Using AI coding tools If you use Claude Code or Codex, then the Restate plugin will automatically be installed. For Cursor, consult the [skills repo README](https://github.com/restatedev/skills). diff --git a/java/templates/java-maven/mvnw b/java/templates/java-maven/mvnw index 19529ddf..bd8896bf 100755 --- a/java/templates/java-maven/mvnw +++ b/java/templates/java-maven/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Optional ENV vars # ----------------- @@ -105,14 +105,17 @@ trim() { printf "%s" "${1}" | tr -d '[:space:]' } +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) @@ -130,7 +133,7 @@ maven-mvnd-*bin.*) distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME @@ -227,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then @@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" diff --git a/java/templates/java-maven/mvnw.cmd b/java/templates/java-maven/mvnw.cmd index b150b91e..5761d948 100644 --- a/java/templates/java-maven/mvnw.cmd +++ b/java/templates/java-maven/mvnw.cmd @@ -19,7 +19,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @@ -40,7 +40,7 @@ @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> @@ -73,16 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" + +$MAVEN_M2_PATH = "$HOME/.m2" if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" } -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { @@ -134,7 +148,33 @@ if ($distributionSha256Sum) { # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { diff --git a/java/templates/java-maven/pom.xml b/java/templates/java-maven/pom.xml index c9e17ac7..9de37fbe 100644 --- a/java/templates/java-maven/pom.xml +++ b/java/templates/java-maven/pom.xml @@ -13,11 +13,11 @@ UTF-8 - 17 - 17 - 2.7.0 + 25 + 2.9.0 + dev.restate @@ -59,35 +59,48 @@ - + org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 + maven-toolchains-plugin + 3.2.0 + + + + select-jdk-toolchain + + + - - - dev.restate - sdk-api-gen - ${restate.version} - - + [25,) - + org.codehaus.mojo exec-maven-plugin - 3.1.0 + 3.6.3 - my.example.Greeter + java + + --enable-native-access=ALL-UNNAMED + --sun-misc-unsafe-memory-access=allow + -classpath + + my.example.Greeter + maven-surefire-plugin 3.5.1 + + + --enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow + diff --git a/java/templates/java-maven/src/main/java/my/example/Greeter.java b/java/templates/java-maven/src/main/java/my/example/Greeter.java index 9efbb825..ccf9eff3 100644 --- a/java/templates/java-maven/src/main/java/my/example/Greeter.java +++ b/java/templates/java-maven/src/main/java/my/example/Greeter.java @@ -1,6 +1,6 @@ package my.example; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.endpoint.Endpoint; @@ -18,12 +18,12 @@ public record Greeting(String name) {} public record GreetingResponse(String message) {} @Handler - public GreetingResponse greet(Context ctx, Greeting req) { + public GreetingResponse greet(Greeting req) { // Durably execute a set of steps; resilient against failures - String greetingId = ctx.random().nextUUID().toString(); - ctx.run("Notification", () -> sendNotification(greetingId, req.name)); - ctx.sleep(Duration.ofSeconds(1)); - ctx.run("Reminder", () -> sendReminder(greetingId, req.name)); + String greetingId = Restate.random().nextUUID().toString(); + Restate.run("Notification", () -> sendNotification(greetingId, req.name)); + Restate.sleep(Duration.ofSeconds(1)); + Restate.run("Reminder", () -> sendReminder(greetingId, req.name)); // Respond to caller return new GreetingResponse("You said hi to " + req.name + "!"); diff --git a/java/templates/java-maven/src/test/java/my/example/GreeterTest.java b/java/templates/java-maven/src/test/java/my/example/GreeterTest.java index c3e42324..94775334 100644 --- a/java/templates/java-maven/src/test/java/my/example/GreeterTest.java +++ b/java/templates/java-maven/src/test/java/my/example/GreeterTest.java @@ -28,7 +28,7 @@ class GreeterTest { @Test @Timeout(value = 10) void testGreet(@RestateClient Client ingressClient) { - var client = GreeterClient.fromClient(ingressClient); + var client = ingressClient.service(Greeter.class); GreetingResponse response = client.greet(new Greeting("Francesco")); assertEquals(response.message(), "You said hi to Francesco!"); diff --git a/java/templates/java-new-api-gradle/.agents/plugins/marketplace.json b/java/templates/java-new-api-gradle/.agents/plugins/marketplace.json deleted file mode 100644 index b9c8574c..00000000 --- a/java/templates/java-new-api-gradle/.agents/plugins/marketplace.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "restatedev-plugin", - "interface": { - "displayName": "Restate" - }, - "plugins": [ - { - "name": "restatedev", - "source": { - "source": "git-subdir", - "url": "https://github.com/restatedev/skills.git", - "path": "./plugins/restatedev", - "ref": "main" - }, - "policy": { - "installation": "AVAILABLE", - "authentication": "ON_INSTALL" - }, - "category": "Coding" - } - ] -} diff --git a/java/templates/java-new-api-gradle/.ai/mcp/mcp.json b/java/templates/java-new-api-gradle/.ai/mcp/mcp.json deleted file mode 100644 index 78a21c5a..00000000 --- a/java/templates/java-new-api-gradle/.ai/mcp/mcp.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mcpServers": { - "restate-docs": { - "url": "https://docs.restate.dev/mcp" - } - } -} \ No newline at end of file diff --git a/java/templates/java-new-api-gradle/.aiassistant/rules/restate.md b/java/templates/java-new-api-gradle/.aiassistant/rules/restate.md deleted file mode 100644 index 599b1e28..00000000 --- a/java/templates/java-new-api-gradle/.aiassistant/rules/restate.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -apply: by model decision -instructions: Guidelines for working with Restate durable services in this project ---- - -# Restate Java project - -This project uses [Restate](https://restate.dev) — a durable execution runtime for resilient services, workflows, and AI agents. Restate captures every completed step so handlers can resume exactly where they left off after crashes, restarts, or retries. - -## Core concepts - -- **Services** — stateless handlers that run deterministically; retries resume from the last durable step. -- **Virtual Objects** — stateful, key-addressed handlers. State lives in Restate; Restate serializes per-key access. -- **Workflows** — long-running, multi-step flows with a single lifecycle per key. -- **Contexts** — every handler receives `Context`, `ObjectContext`, `WorkflowContext` (or their `Shared` variants). All non-deterministic work (I/O, random, time, RPC) must go through the context so it is journaled. - -## Rules for this codebase - -- Never perform side effects directly. Wrap I/O, randomness, timers, and RPCs in `ctx.run(...)`, `ctx.sleep(...)`, etc., so they are durable. -- Keep handler code deterministic between journal entries: same inputs must produce the same sequence of context calls. -- Prefer the typed client APIs (`XxxClient.fromContext(ctx, key)`) for service-to-service calls instead of raw HTTP. -- Serializable inputs/outputs only — use Jackson-compatible POJOs or records. - -## Getting more detail - -For API reference, lifecycle semantics, debugging, migration guidance, Kafka/idempotency patterns, and framework integrations (Quarkus, Spring Boot), query the **`restate-docs` MCP server** configured for this project (`.ai/mcp/mcp.json`). It's bound to `https://docs.restate.dev/mcp` and can search the full documentation on demand. - -Useful entry points the MCP server can resolve: -- SDK API and pitfalls -- Designing services and picking a service type -- Invocation lifecycle, cancellation, idempotency, sends, Kafka -- Debugging stuck invocations and journal mismatches diff --git a/java/templates/java-new-api-gradle/.claude/settings.json b/java/templates/java-new-api-gradle/.claude/settings.json deleted file mode 100644 index 1c69c4f5..00000000 --- a/java/templates/java-new-api-gradle/.claude/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extraKnownMarketplaces": { - "restatedev-plugin": { - "source": { - "source": "github", - "repo": "restatedev/skills" - } - } - }, - "enabledPlugins": { - "restatedev@restatedev-plugin": true - } -} diff --git a/java/templates/java-new-api-gradle/.gitignore b/java/templates/java-new-api-gradle/.gitignore deleted file mode 100644 index 135eaf77..00000000 --- a/java/templates/java-new-api-gradle/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# 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 - -*.iml - -# Unignore the gradle wrapper -!gradle/wrapper/gradle-wrapper.jar \ No newline at end of file diff --git a/java/templates/java-new-api-gradle/README.md b/java/templates/java-new-api-gradle/README.md deleted file mode 100644 index 444cff37..00000000 --- a/java/templates/java-new-api-gradle/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Hello world - Java HTTP example - -Sample project configuration of a Restate service using the Java SDK and HTTP server. - -Have a look at the [Java Quickstart guide](https://docs.restate.dev/get_started/quickstart?sdk=java) for more information on how to use this project. - -To run: - -```shell -./gradlew run -``` - -## Using AI coding tools - -If you use Claude Code or Codex, then the Restate plugin will automatically be installed. For Cursor, consult the [skills repo README](https://github.com/restatedev/skills). - -Plugin repo: https://github.com/restatedev/skills/tree/main \ No newline at end of file diff --git a/java/templates/java-new-api-gradle/build.gradle.kts b/java/templates/java-new-api-gradle/build.gradle.kts deleted file mode 100644 index bdf3bbbe..00000000 --- a/java/templates/java-new-api-gradle/build.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -plugins { - java - application -} - -repositories { - mavenCentral() -} - -val restateVersion = "2.7.0" - -dependencies { - // Restate SDK - implementation("dev.restate:sdk-java-http:$restateVersion") - - // Logging - implementation("org.apache.logging.log4j:log4j-api:2.24.1") - - // JUnit (API & test launcher) - testImplementation("org.junit.jupiter:junit-jupiter:5.14.1") - testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.14.2") - testImplementation("dev.restate:sdk-testing:$restateVersion") -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } -} - -// Set main class -application { - mainClass.set("my.example.Greeter") -} - -tasks.named("test") { - useJUnitPlatform() -} diff --git a/java/templates/java-new-api-gradle/gradle/wrapper/gradle-wrapper.jar b/java/templates/java-new-api-gradle/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 1b33c55b..00000000 Binary files a/java/templates/java-new-api-gradle/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/java/templates/java-new-api-gradle/gradle/wrapper/gradle-wrapper.properties b/java/templates/java-new-api-gradle/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 37f78a6a..00000000 --- a/java/templates/java-new-api-gradle/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-9.3.1-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/java/templates/java-new-api-gradle/gradlew b/java/templates/java-new-api-gradle/gradlew deleted file mode 100755 index 23d15a93..00000000 --- a/java/templates/java-new-api-gradle/gradlew +++ /dev/null @@ -1,251 +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. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# 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/platforms/jvm/plugins-application/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 -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || 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="\\\"\\\"" - - -# 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, 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" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# 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/java/templates/java-new-api-gradle/gradlew.bat b/java/templates/java-new-api-gradle/gradlew.bat deleted file mode 100644 index 5eed7ee8..00000000 --- a/java/templates/java-new-api-gradle/gradlew.bat +++ /dev/null @@ -1,94 +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 -@rem SPDX-License-Identifier: Apache-2.0 -@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. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -: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/java/templates/java-new-api-gradle/settings.gradle.kts b/java/templates/java-new-api-gradle/settings.gradle.kts deleted file mode 100644 index 778b3b8e..00000000 --- a/java/templates/java-new-api-gradle/settings.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version("1.0.0") -} \ No newline at end of file diff --git a/java/templates/java-new-api-gradle/src/main/java/my/example/Greeter.java b/java/templates/java-new-api-gradle/src/main/java/my/example/Greeter.java deleted file mode 100644 index 663f19e3..00000000 --- a/java/templates/java-new-api-gradle/src/main/java/my/example/Greeter.java +++ /dev/null @@ -1,35 +0,0 @@ -package my.example; - -import dev.restate.sdk.Restate; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; -import dev.restate.sdk.endpoint.Endpoint; -import dev.restate.sdk.http.vertx.RestateHttpServer; - -import java.time.Duration; - -import static my.example.Utils.sendNotification; -import static my.example.Utils.sendReminder; - -@Service -public class Greeter { - - public record Greeting(String name) {} - public record GreetingResponse(String message) {} - - @Handler - public GreetingResponse greet(Greeting req) { - // Durably execute a set of steps; resilient against failures - String greetingId = Restate.random().nextUUID().toString(); - Restate.run("Notification", () -> sendNotification(greetingId, req.name)); - Restate.sleep(Duration.ofSeconds(1)); - Restate.run("Reminder", () -> sendReminder(greetingId, req.name)); - - // Respond to caller - return new GreetingResponse("You said hi to " + req.name + "!"); - } - - public static void main(String[] args) { - RestateHttpServer.listen(Endpoint.bind(new Greeter())); - } -} diff --git a/java/templates/java-new-api-gradle/src/main/java/my/example/Utils.java b/java/templates/java-new-api-gradle/src/main/java/my/example/Utils.java deleted file mode 100644 index 7e7138d7..00000000 --- a/java/templates/java-new-api-gradle/src/main/java/my/example/Utils.java +++ /dev/null @@ -1,22 +0,0 @@ -package my.example; - -// You can remove this file. -// It's only purpose is providing stubs for the template. - -class Utils { - public static void sendNotification(String greetingId, String name){ - if (Math.random() < 0.7 && name.equals("Alice")) { // 70% chance of failure - System.out.println("[👻 SIMULATED] Failed to send notification: " + greetingId + " - " + name); - throw new RuntimeException("[👻 SIMULATED] Failed to send notification: " + greetingId + " - " + name); - } - System.out.println("Notification sent: " + greetingId + " - " + name); - } - - public static void sendReminder(String greetingId, String name){ - if (Math.random() < 0.7 && name.equals("Alice")) { // 70% chance of failure - System.out.println("[👻 SIMULATED] Failed to send reminder: " + greetingId); - throw new RuntimeException("[👻 SIMULATED] Failed to send reminder: " + greetingId); - } - System.out.println("Reminder sent: " + greetingId); - } -} \ No newline at end of file diff --git a/java/templates/java-new-api-gradle/src/main/resources/log4j2.properties b/java/templates/java-new-api-gradle/src/main/resources/log4j2.properties deleted file mode 100644 index 536d1d39..00000000 --- a/java/templates/java-new-api-gradle/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{restateInvocationTarget}]}%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/java/templates/java-new-api-gradle/src/test/java/my/example/GreeterTest.java b/java/templates/java-new-api-gradle/src/test/java/my/example/GreeterTest.java deleted file mode 100644 index bc473ca7..00000000 --- a/java/templates/java-new-api-gradle/src/test/java/my/example/GreeterTest.java +++ /dev/null @@ -1,36 +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.example; - -import dev.restate.client.Client; -import dev.restate.sdk.testing.BindService; -import dev.restate.sdk.testing.RestateClient; -import dev.restate.sdk.testing.RestateTest; -import my.example.Greeter.GreetingResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; - -import static my.example.Greeter.Greeting; -import static org.junit.jupiter.api.Assertions.assertEquals; - -@RestateTest -class GreeterTest { - - @BindService - private final Greeter greeter = new Greeter(); - - @Test - @Timeout(value = 10) - void testGreet(@RestateClient Client ingressClient) { - var client = ingressClient.service(Greeter.class); - - GreetingResponse response = client.greet(new Greeting("Francesco")); - assertEquals(response.message(), "You said hi to Francesco!"); - } -} diff --git a/java/templates/java-new-api-maven-spring-boot/.agents/plugins/marketplace.json b/java/templates/java-new-api-maven-spring-boot/.agents/plugins/marketplace.json deleted file mode 100644 index b9c8574c..00000000 --- a/java/templates/java-new-api-maven-spring-boot/.agents/plugins/marketplace.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "restatedev-plugin", - "interface": { - "displayName": "Restate" - }, - "plugins": [ - { - "name": "restatedev", - "source": { - "source": "git-subdir", - "url": "https://github.com/restatedev/skills.git", - "path": "./plugins/restatedev", - "ref": "main" - }, - "policy": { - "installation": "AVAILABLE", - "authentication": "ON_INSTALL" - }, - "category": "Coding" - } - ] -} diff --git a/java/templates/java-new-api-maven-spring-boot/.ai/mcp/mcp.json b/java/templates/java-new-api-maven-spring-boot/.ai/mcp/mcp.json deleted file mode 100644 index 78a21c5a..00000000 --- a/java/templates/java-new-api-maven-spring-boot/.ai/mcp/mcp.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mcpServers": { - "restate-docs": { - "url": "https://docs.restate.dev/mcp" - } - } -} \ No newline at end of file diff --git a/java/templates/java-new-api-maven-spring-boot/.aiassistant/rules/restate.md b/java/templates/java-new-api-maven-spring-boot/.aiassistant/rules/restate.md deleted file mode 100644 index 599b1e28..00000000 --- a/java/templates/java-new-api-maven-spring-boot/.aiassistant/rules/restate.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -apply: by model decision -instructions: Guidelines for working with Restate durable services in this project ---- - -# Restate Java project - -This project uses [Restate](https://restate.dev) — a durable execution runtime for resilient services, workflows, and AI agents. Restate captures every completed step so handlers can resume exactly where they left off after crashes, restarts, or retries. - -## Core concepts - -- **Services** — stateless handlers that run deterministically; retries resume from the last durable step. -- **Virtual Objects** — stateful, key-addressed handlers. State lives in Restate; Restate serializes per-key access. -- **Workflows** — long-running, multi-step flows with a single lifecycle per key. -- **Contexts** — every handler receives `Context`, `ObjectContext`, `WorkflowContext` (or their `Shared` variants). All non-deterministic work (I/O, random, time, RPC) must go through the context so it is journaled. - -## Rules for this codebase - -- Never perform side effects directly. Wrap I/O, randomness, timers, and RPCs in `ctx.run(...)`, `ctx.sleep(...)`, etc., so they are durable. -- Keep handler code deterministic between journal entries: same inputs must produce the same sequence of context calls. -- Prefer the typed client APIs (`XxxClient.fromContext(ctx, key)`) for service-to-service calls instead of raw HTTP. -- Serializable inputs/outputs only — use Jackson-compatible POJOs or records. - -## Getting more detail - -For API reference, lifecycle semantics, debugging, migration guidance, Kafka/idempotency patterns, and framework integrations (Quarkus, Spring Boot), query the **`restate-docs` MCP server** configured for this project (`.ai/mcp/mcp.json`). It's bound to `https://docs.restate.dev/mcp` and can search the full documentation on demand. - -Useful entry points the MCP server can resolve: -- SDK API and pitfalls -- Designing services and picking a service type -- Invocation lifecycle, cancellation, idempotency, sends, Kafka -- Debugging stuck invocations and journal mismatches diff --git a/java/templates/java-new-api-maven-spring-boot/.claude/settings.json b/java/templates/java-new-api-maven-spring-boot/.claude/settings.json deleted file mode 100644 index 1c69c4f5..00000000 --- a/java/templates/java-new-api-maven-spring-boot/.claude/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extraKnownMarketplaces": { - "restatedev-plugin": { - "source": { - "source": "github", - "repo": "restatedev/skills" - } - } - }, - "enabledPlugins": { - "restatedev@restatedev-plugin": true - } -} diff --git a/java/templates/java-new-api-maven-spring-boot/.gitattributes b/java/templates/java-new-api-maven-spring-boot/.gitattributes deleted file mode 100644 index 3b41682a..00000000 --- a/java/templates/java-new-api-maven-spring-boot/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -/mvnw text eol=lf -*.cmd text eol=crlf diff --git a/java/templates/java-new-api-maven-spring-boot/.gitignore b/java/templates/java-new-api-maven-spring-boot/.gitignore deleted file mode 100644 index 549e00a2..00000000 --- a/java/templates/java-new-api-maven-spring-boot/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ diff --git a/java/templates/java-new-api-maven-spring-boot/.mvn/wrapper/maven-wrapper.properties b/java/templates/java-new-api-maven-spring-boot/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index d58dfb70..00000000 --- a/java/templates/java-new-api-maven-spring-boot/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -wrapperVersion=3.3.2 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/java/templates/java-new-api-maven-spring-boot/README.md b/java/templates/java-new-api-maven-spring-boot/README.md deleted file mode 100644 index e512d5db..00000000 --- a/java/templates/java-new-api-maven-spring-boot/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Hello world - Spring Boot example - -Sample project configuration of a Restate service using the Java SDK and Spring Boot. - -Have a look at the [Java Quickstart guide](https://docs.restate.dev/get_started/quickstart?sdk=java) for more information on how to use this project. - -## Starting the service - -To start the service, simply run: - -```shell -$ mvn compile spring-boot:run -``` - -## Using AI coding tools - -If you use Claude Code or Codex, then the Restate plugin will automatically be installed. For Cursor, consult the [skills repo README](https://github.com/restatedev/skills). - -Plugin repo: https://github.com/restatedev/skills/tree/main \ No newline at end of file diff --git a/java/templates/java-new-api-maven-spring-boot/mvnw b/java/templates/java-new-api-maven-spring-boot/mvnw deleted file mode 100755 index 19529ddf..00000000 --- a/java/templates/java-new-api-maven-spring-boot/mvnw +++ /dev/null @@ -1,259 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - 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" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" -MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/java/templates/java-new-api-maven-spring-boot/mvnw.cmd b/java/templates/java-new-api-maven-spring-boot/mvnw.cmd deleted file mode 100644 index 249bdf38..00000000 --- a/java/templates/java-new-api-maven-spring-boot/mvnw.cmd +++ /dev/null @@ -1,149 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" -} -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/java/templates/java-new-api-maven-spring-boot/pom.xml b/java/templates/java-new-api-maven-spring-boot/pom.xml deleted file mode 100644 index 2d521a09..00000000 --- a/java/templates/java-new-api-maven-spring-boot/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.5.6 - - - com.example - restate-starter - 0.0.1-SNAPSHOT - restate-starter - Starter for Spring Boot + Restate - - - - - - - - - - - - - - - 17 - 2.7.0 - - - - org.springframework.boot - spring-boot-starter-web - - - dev.restate - sdk-spring-boot-starter - ${restate.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - dev.restate - sdk-testing - ${restate.version} - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java b/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java deleted file mode 100644 index 628cc823..00000000 --- a/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.example.restatestarter; - -import dev.restate.sdk.Restate; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.springboot.RestateService; -import org.springframework.beans.factory.annotation.Value; - -import java.time.Duration; - -import static com.example.restatestarter.Utils.sendNotification; -import static com.example.restatestarter.Utils.sendReminder; - -@RestateService -public class Greeter { - - @Value("${greetingPrefix}") - private String greetingPrefix; - - public record Greeting(String name) {} - public record GreetingResponse(String message) {} - - @Handler - public GreetingResponse greet(Greeting req) { - // Durably execute a set of steps; resilient against failures - String greetingId = Restate.random().nextUUID().toString(); - Restate.run("Notification", () -> sendNotification(greetingId, req.name)); - Restate.sleep(Duration.ofSeconds(1)); - Restate.run("Reminder", () -> sendReminder(greetingId, req.name)); - - // Respond to caller - return new GreetingResponse("You said " + greetingPrefix + " to " + req.name + "!"); - } -} - - diff --git a/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/HelloController.java b/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/HelloController.java deleted file mode 100644 index 3ebdb044..00000000 --- a/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/HelloController.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.restatestarter; - -import com.example.restatestarter.Greeter.Greeting; -import dev.restate.client.Client; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class HelloController { - - private final Client restateClient; - - public HelloController(Client restateClient) { - this.restateClient = restateClient; - } - - @GetMapping("/") - public String index() { - return restateClient.service(Greeter.class).greet(new Greeting("Alice")).message() + " from Spring Boot!"; - } - -} \ No newline at end of file diff --git a/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/RestateStarterApplication.java b/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/RestateStarterApplication.java deleted file mode 100644 index 9bc8f2b0..00000000 --- a/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/RestateStarterApplication.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.restatestarter; - -import dev.restate.sdk.springboot.EnableRestate; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -@EnableRestate -public class RestateStarterApplication { - - public static void main(String[] args) { - SpringApplication.run(RestateStarterApplication.class, args); - } - -} diff --git a/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/Utils.java b/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/Utils.java deleted file mode 100644 index 92d8c8d3..00000000 --- a/java/templates/java-new-api-maven-spring-boot/src/main/java/com/example/restatestarter/Utils.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.restatestarter; - -// You can remove this file. -// It's only purpose is providing stubs for the template. - -class Utils { - public static void sendNotification(String greetingId, String name){ - if (Math.random() < 0.7 && name.equals("Alice")) { // 70% chance of failure - System.out.println("[👻 SIMULATED] Failed to send notification: " + greetingId + " - " + name); - throw new RuntimeException("[👻 SIMULATED] Failed to send notification: " + greetingId + " - " + name); - } - System.out.println("Notification sent: " + greetingId + " - " + name); - } - - public static void sendReminder(String greetingId, String name){ - if (Math.random() < 0.7 && name.equals("Alice")) { // 70% chance of failure - System.out.println("[👻 SIMULATED] Failed to send reminder: " + greetingId); - throw new RuntimeException("[👻 SIMULATED] Failed to send reminder: " + greetingId); - } - System.out.println("Reminder sent: " + greetingId); - } -} \ No newline at end of file diff --git a/java/templates/java-new-api-maven-spring-boot/src/main/resources/application.properties b/java/templates/java-new-api-maven-spring-boot/src/main/resources/application.properties deleted file mode 100644 index 0372747d..00000000 --- a/java/templates/java-new-api-maven-spring-boot/src/main/resources/application.properties +++ /dev/null @@ -1,11 +0,0 @@ -spring.application.name=restate-starter -greetingPrefix=hi - -# Restate SDK port (to expose Greeter) -restate.sdk.http.port=9080 - -# Where Restate ingress is available -restate.client.base-uri=http://localhost:8080 - -# The spring server (for Hello Controller) -server.port=3000 \ No newline at end of file diff --git a/java/templates/java-new-api-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java b/java/templates/java-new-api-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java deleted file mode 100644 index 54696d99..00000000 --- a/java/templates/java-new-api-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.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 com.example.restatestarter; - -import com.example.restatestarter.Greeter.Greeting; -import dev.restate.client.Client; -import dev.restate.sdk.testing.*; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest( - classes = Greeter.class, - properties = {"greetingPrefix=ciao"}) -@RestateTest -public class GreeterTest { - - @Autowired @BindService private Greeter greeter; - - @Test - @Timeout(value = 10) - void greet(@RestateClient Client ingressClient) { - var client = ingressClient.service(Greeter.class); - - var response = client.greet(new Greeting("Francesco")); - assertThat(response.message()).isEqualTo("You said ciao to Francesco!"); - } -} diff --git a/java/templates/java-new-api-maven/.agents/plugins/marketplace.json b/java/templates/java-new-api-maven/.agents/plugins/marketplace.json deleted file mode 100644 index b9c8574c..00000000 --- a/java/templates/java-new-api-maven/.agents/plugins/marketplace.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "restatedev-plugin", - "interface": { - "displayName": "Restate" - }, - "plugins": [ - { - "name": "restatedev", - "source": { - "source": "git-subdir", - "url": "https://github.com/restatedev/skills.git", - "path": "./plugins/restatedev", - "ref": "main" - }, - "policy": { - "installation": "AVAILABLE", - "authentication": "ON_INSTALL" - }, - "category": "Coding" - } - ] -} diff --git a/java/templates/java-new-api-maven/.ai/mcp/mcp.json b/java/templates/java-new-api-maven/.ai/mcp/mcp.json deleted file mode 100644 index 78a21c5a..00000000 --- a/java/templates/java-new-api-maven/.ai/mcp/mcp.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mcpServers": { - "restate-docs": { - "url": "https://docs.restate.dev/mcp" - } - } -} \ No newline at end of file diff --git a/java/templates/java-new-api-maven/.aiassistant/rules/restate.md b/java/templates/java-new-api-maven/.aiassistant/rules/restate.md deleted file mode 100644 index 599b1e28..00000000 --- a/java/templates/java-new-api-maven/.aiassistant/rules/restate.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -apply: by model decision -instructions: Guidelines for working with Restate durable services in this project ---- - -# Restate Java project - -This project uses [Restate](https://restate.dev) — a durable execution runtime for resilient services, workflows, and AI agents. Restate captures every completed step so handlers can resume exactly where they left off after crashes, restarts, or retries. - -## Core concepts - -- **Services** — stateless handlers that run deterministically; retries resume from the last durable step. -- **Virtual Objects** — stateful, key-addressed handlers. State lives in Restate; Restate serializes per-key access. -- **Workflows** — long-running, multi-step flows with a single lifecycle per key. -- **Contexts** — every handler receives `Context`, `ObjectContext`, `WorkflowContext` (or their `Shared` variants). All non-deterministic work (I/O, random, time, RPC) must go through the context so it is journaled. - -## Rules for this codebase - -- Never perform side effects directly. Wrap I/O, randomness, timers, and RPCs in `ctx.run(...)`, `ctx.sleep(...)`, etc., so they are durable. -- Keep handler code deterministic between journal entries: same inputs must produce the same sequence of context calls. -- Prefer the typed client APIs (`XxxClient.fromContext(ctx, key)`) for service-to-service calls instead of raw HTTP. -- Serializable inputs/outputs only — use Jackson-compatible POJOs or records. - -## Getting more detail - -For API reference, lifecycle semantics, debugging, migration guidance, Kafka/idempotency patterns, and framework integrations (Quarkus, Spring Boot), query the **`restate-docs` MCP server** configured for this project (`.ai/mcp/mcp.json`). It's bound to `https://docs.restate.dev/mcp` and can search the full documentation on demand. - -Useful entry points the MCP server can resolve: -- SDK API and pitfalls -- Designing services and picking a service type -- Invocation lifecycle, cancellation, idempotency, sends, Kafka -- Debugging stuck invocations and journal mismatches diff --git a/java/templates/java-new-api-maven/.claude/settings.json b/java/templates/java-new-api-maven/.claude/settings.json deleted file mode 100644 index 1c69c4f5..00000000 --- a/java/templates/java-new-api-maven/.claude/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extraKnownMarketplaces": { - "restatedev-plugin": { - "source": { - "source": "github", - "repo": "restatedev/skills" - } - } - }, - "enabledPlugins": { - "restatedev@restatedev-plugin": true - } -} diff --git a/java/templates/java-new-api-maven/.gitignore b/java/templates/java-new-api-maven/.gitignore deleted file mode 100644 index 1a05f502..00000000 --- a/java/templates/java-new-api-maven/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# 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 target -greeter/target - -*.iml \ No newline at end of file diff --git a/java/templates/java-new-api-maven/.mvn/wrapper/maven-wrapper.properties b/java/templates/java-new-api-maven/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index d58dfb70..00000000 --- a/java/templates/java-new-api-maven/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -wrapperVersion=3.3.2 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/java/templates/java-new-api-maven/README.md b/java/templates/java-new-api-maven/README.md deleted file mode 100644 index 16c2c415..00000000 --- a/java/templates/java-new-api-maven/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Hello world - Java HTTP example - -Sample project configuration of a Restate service using the Java SDK and HTTP server. - -Have a look at the [Java Quickstart guide](https://docs.restate.dev/get_started/quickstart?sdk=java) for more information on how to use this project. - -To run: - -```shell -mvn compile exec:java -``` - -## Using AI coding tools - -If you use Claude Code or Codex, then the Restate plugin will automatically be installed. For Cursor, consult the [skills repo README](https://github.com/restatedev/skills). - -Plugin repo: https://github.com/restatedev/skills/tree/main \ No newline at end of file diff --git a/java/templates/java-new-api-maven/mvnw b/java/templates/java-new-api-maven/mvnw deleted file mode 100755 index 19529ddf..00000000 --- a/java/templates/java-new-api-maven/mvnw +++ /dev/null @@ -1,259 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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 -# -# http://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. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - 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" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" -MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/java/templates/java-new-api-maven/mvnw.cmd b/java/templates/java-new-api-maven/mvnw.cmd deleted file mode 100644 index b150b91e..00000000 --- a/java/templates/java-new-api-maven/mvnw.cmd +++ /dev/null @@ -1,149 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" -} -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/java/templates/java-new-api-maven/pom.xml b/java/templates/java-new-api-maven/pom.xml deleted file mode 100644 index b9ca55d3..00000000 --- a/java/templates/java-new-api-maven/pom.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - 4.0.0 - - my.example - greeter - 1.0-SNAPSHOT - - greeter - http://www.example.com - - - UTF-8 - 17 - 17 - 2.7.0 - - - - - dev.restate - sdk-java-http - ${restate.version} - - - - - org.apache.logging.log4j - log4j-api - 2.24.1 - - - - org.junit.jupiter - junit-jupiter - test - - - dev.restate - sdk-testing - ${restate.version} - - - - - - - - org.junit - junit-bom - 5.11.3 - pom - import - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 3.1.0 - - my.example.Greeter - - - - - maven-surefire-plugin - 3.5.1 - - - - - diff --git a/java/templates/java-new-api-maven/src/main/java/my/example/Greeter.java b/java/templates/java-new-api-maven/src/main/java/my/example/Greeter.java deleted file mode 100644 index e265fe69..00000000 --- a/java/templates/java-new-api-maven/src/main/java/my/example/Greeter.java +++ /dev/null @@ -1,35 +0,0 @@ -package my.example; - -import dev.restate.sdk.Restate; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; -import dev.restate.sdk.endpoint.Endpoint; -import dev.restate.sdk.http.vertx.RestateHttpServer; - -import java.time.Duration; - -import static my.example.Utils.sendNotification; -import static my.example.Utils.sendReminder; - -@Service -public class Greeter { - - public record Greeting(String name) {} - public record GreetingResponse(String message) {} - - @Handler - public GreetingResponse greet(Greeting req) { - // Durably execute a set of steps; resilient against failures - String greetingId = Restate.random().nextUUID().toString(); - Restate.run("Notification", () -> sendNotification(greetingId, req.name)); - Restate.sleep(Duration.ofSeconds(1)); - Restate.run("Reminder", () -> sendReminder(greetingId, req.name)); - - // Respond to caller - return new GreetingResponse("You said hi to " + req.name + "!"); - } - - public static void main(String[] args) { - RestateHttpServer.listen(Endpoint.bind(new Greeter())); - } -} diff --git a/java/templates/java-new-api-maven/src/main/java/my/example/Utils.java b/java/templates/java-new-api-maven/src/main/java/my/example/Utils.java deleted file mode 100644 index 7e7138d7..00000000 --- a/java/templates/java-new-api-maven/src/main/java/my/example/Utils.java +++ /dev/null @@ -1,22 +0,0 @@ -package my.example; - -// You can remove this file. -// It's only purpose is providing stubs for the template. - -class Utils { - public static void sendNotification(String greetingId, String name){ - if (Math.random() < 0.7 && name.equals("Alice")) { // 70% chance of failure - System.out.println("[👻 SIMULATED] Failed to send notification: " + greetingId + " - " + name); - throw new RuntimeException("[👻 SIMULATED] Failed to send notification: " + greetingId + " - " + name); - } - System.out.println("Notification sent: " + greetingId + " - " + name); - } - - public static void sendReminder(String greetingId, String name){ - if (Math.random() < 0.7 && name.equals("Alice")) { // 70% chance of failure - System.out.println("[👻 SIMULATED] Failed to send reminder: " + greetingId); - throw new RuntimeException("[👻 SIMULATED] Failed to send reminder: " + greetingId); - } - System.out.println("Reminder sent: " + greetingId); - } -} \ No newline at end of file diff --git a/java/templates/java-new-api-maven/src/main/resources/log4j2.properties b/java/templates/java-new-api-maven/src/main/resources/log4j2.properties deleted file mode 100644 index 536d1d39..00000000 --- a/java/templates/java-new-api-maven/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{restateInvocationTarget}]}%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/java/templates/java-new-api-maven/src/test/java/my/example/GreeterTest.java b/java/templates/java-new-api-maven/src/test/java/my/example/GreeterTest.java deleted file mode 100644 index 94775334..00000000 --- a/java/templates/java-new-api-maven/src/test/java/my/example/GreeterTest.java +++ /dev/null @@ -1,36 +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.example; - -import dev.restate.client.Client; -import dev.restate.sdk.testing.BindService; -import dev.restate.sdk.testing.RestateClient; -import dev.restate.sdk.testing.RestateTest; -import my.example.Greeter.Greeting; -import my.example.Greeter.GreetingResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@RestateTest -class GreeterTest { - - @BindService - private final Greeter greeter = new Greeter(); - - @Test - @Timeout(value = 10) - void testGreet(@RestateClient Client ingressClient) { - var client = ingressClient.service(Greeter.class); - - GreetingResponse response = client.greet(new Greeting("Francesco")); - assertEquals(response.message(), "You said hi to Francesco!"); - } -} diff --git a/java/tutorials/tour-of-orchestration-java/build.gradle.kts b/java/tutorials/tour-of-orchestration-java/build.gradle.kts index 516d321c..33085174 100644 --- a/java/tutorials/tour-of-orchestration-java/build.gradle.kts +++ b/java/tutorials/tour-of-orchestration-java/build.gradle.kts @@ -1,18 +1,16 @@ plugins { java application - id("com.diffplug.spotless") version "6.25.0" + id("com.diffplug.spotless") version "8.8.0" } repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-java-http:$restateVersion") @@ -22,12 +20,14 @@ dependencies { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(25)) } } // Set main class application { + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") if (project.hasProperty("mainClass")) { mainClass.set(project.property("mainClass") as String) } else { @@ -35,6 +35,12 @@ application { } } +tasks.named("test") { + useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} + spotless { java { googleJavaFormat() diff --git a/java/tutorials/tour-of-orchestration-java/gradle/wrapper/gradle-wrapper.jar b/java/tutorials/tour-of-orchestration-java/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/java/tutorials/tour-of-orchestration-java/gradle/wrapper/gradle-wrapper.jar and b/java/tutorials/tour-of-orchestration-java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/tutorials/tour-of-orchestration-java/gradle/wrapper/gradle-wrapper.properties b/java/tutorials/tour-of-orchestration-java/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/java/tutorials/tour-of-orchestration-java/gradle/wrapper/gradle-wrapper.properties +++ b/java/tutorials/tour-of-orchestration-java/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/tutorials/tour-of-orchestration-java/gradlew b/java/tutorials/tour-of-orchestration-java/gradlew index faf93008..249efbb0 100755 --- a/java/tutorials/tour-of-orchestration-java/gradlew +++ b/java/tutorials/tour-of-orchestration-java/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/java/tutorials/tour-of-orchestration-java/gradlew.bat b/java/tutorials/tour-of-orchestration-java/gradlew.bat index 9b42019c..8508ef68 100644 --- a/java/tutorials/tour-of-orchestration-java/gradlew.bat +++ b/java/tutorials/tour-of-orchestration-java/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/ConcertTicketingService.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/ConcertTicketingService.java index 05a2eb69..4245c001 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/ConcertTicketingService.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/ConcertTicketingService.java @@ -1,6 +1,6 @@ package my.example.communication; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import my.example.auxiliary.types.PurchaseTicketRequest; @@ -9,15 +9,16 @@ public class ConcertTicketingService { @Handler - public String buy(Context ctx, PurchaseTicketRequest req) { + public String buy(PurchaseTicketRequest req) { // Request-response call - wait for payment to complete - String payRef = PaymentServiceClient.fromContext(ctx).charge(req).await(); + String payRef = Restate.service(PaymentService.class).charge(req); // One-way message - fire and forget ticket delivery - EmailServiceClient.fromContext(ctx).send().emailTicket(req); + Restate.serviceHandle(EmailService.class).send(EmailService::emailTicket, req); // Delayed message - schedule reminder for day before concert - EmailServiceClient.fromContext(ctx).send().sendReminder(req, req.dayBefore()); + Restate.serviceHandle(EmailService.class) + .send(EmailService::sendReminder, req, req.dayBefore()); return "Ticket purchased successfully with payment reference: " + payRef; } diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/EmailService.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/EmailService.java index 22d16c29..4152ad68 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/EmailService.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/EmailService.java @@ -1,6 +1,5 @@ package my.example.communication; -import dev.restate.sdk.Context; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import my.example.auxiliary.types.PurchaseTicketRequest; @@ -9,13 +8,13 @@ public class EmailService { @Handler - public void emailTicket(Context ctx, PurchaseTicketRequest req) { + public void emailTicket(PurchaseTicketRequest req) { System.out.printf( "Sending ticket to %s for concert on %s", req.customerEmail(), req.concertDate()); } @Handler - public void sendReminder(Context ctx, PurchaseTicketRequest req) { + public void sendReminder(PurchaseTicketRequest req) { System.out.printf( "Sending reminder for concert on %s to %s", req.concertDate(), req.customerEmail()); } diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/PaymentService.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/PaymentService.java index 039fbda7..b66c88c8 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/PaymentService.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/communication/PaymentService.java @@ -1,6 +1,6 @@ package my.example.communication; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import my.example.auxiliary.types.PurchaseTicketRequest; @@ -9,9 +9,9 @@ public class PaymentService { @Handler - public String charge(Context ctx, PurchaseTicketRequest req) { + public String charge(PurchaseTicketRequest req) { // Simulate payment processing - String paymentId = ctx.random().nextUUID().toString(); + String paymentId = Restate.random().nextUUID().toString(); System.out.printf( "Processing payment for ticket %s with payment ID %s", req.ticketId(), paymentId); return paymentId; diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/concurrenttasks/ParallelSubscriptionService.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/concurrenttasks/ParallelSubscriptionService.java index ebece1f2..5eb6f1c0 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/concurrenttasks/ParallelSubscriptionService.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/concurrenttasks/ParallelSubscriptionService.java @@ -3,8 +3,8 @@ import static my.example.auxiliary.clients.PaymentClient.createRecurringPayment; import static my.example.auxiliary.clients.SubscriptionClient.createSubscription; -import dev.restate.sdk.Context; import dev.restate.sdk.DurableFuture; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import java.util.ArrayList; @@ -16,16 +16,16 @@ public class ParallelSubscriptionService { @Handler - public SubscriptionResult add(Context ctx, SubscriptionRequest req) { - var paymentId = ctx.random().nextUUID().toString(); + public SubscriptionResult add(SubscriptionRequest req) { + var paymentId = Restate.random().nextUUID().toString(); var payRef = - ctx.run("pay", String.class, () -> createRecurringPayment(req.creditCard(), paymentId)); + Restate.run("pay", String.class, () -> createRecurringPayment(req.creditCard(), paymentId)); // Start all subscriptions in parallel List> subscriptionFutures = new ArrayList<>(); for (String subscription : req.subscriptions()) { subscriptionFutures.add( - ctx.runAsync( + Restate.runAsync( "add-" + subscription, () -> createSubscription(req.userId(), subscription, payRef))); } diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/events/Payments.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/events/Payments.java index 9c1ac943..032206f4 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/events/Payments.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/events/Payments.java @@ -2,7 +2,7 @@ import static my.example.auxiliary.clients.PaymentClient.initPayment; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import my.example.auxiliary.types.ConfirmationRequest; @@ -13,13 +13,13 @@ public class Payments { @Handler - public PaymentResult process(Context ctx, PaymentRequest req) { + public PaymentResult process(PaymentRequest req) { // Create awakeable to wait for webhook payment confirmation - var confirmation = ctx.awakeable(PaymentResult.class); + var confirmation = Restate.awakeable(PaymentResult.class); // Initiate payment with external provider (Stripe, PayPal, etc.) - var paymentId = ctx.random().nextUUID().toString(); - ctx.run(() -> initPayment(req, paymentId, confirmation.id())); + var paymentId = Restate.random().nextUUID().toString(); + Restate.run("pay", () -> initPayment(req, paymentId, confirmation.id())); // Wait for external payment provider to call our webhook return confirmation.await(); @@ -27,8 +27,8 @@ public PaymentResult process(Context ctx, PaymentRequest req) { // Webhook handler called by external payment provider @Handler - public void confirm(Context ctx, ConfirmationRequest confirmation) { + public void confirm(ConfirmationRequest confirmation) { // Resolve the awakeable to continue the payment flow - ctx.awakeableHandle(confirmation.id()).resolve(PaymentResult.class, confirmation.result()); + Restate.awakeableHandle(confirmation.id()).resolve(PaymentResult.class, confirmation.result()); } } diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/getstarted/SubscriptionService.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/getstarted/SubscriptionService.java index de46b1d0..20820748 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/getstarted/SubscriptionService.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/getstarted/SubscriptionService.java @@ -3,7 +3,7 @@ import static my.example.auxiliary.clients.PaymentClient.createRecurringPayment; import static my.example.auxiliary.clients.SubscriptionClient.createSubscription; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import my.example.auxiliary.types.SubscriptionRequest; @@ -12,14 +12,15 @@ public class SubscriptionService { @Handler - public void add(Context ctx, SubscriptionRequest req) { - var paymentId = ctx.random().nextUUID().toString(); + public void add(SubscriptionRequest req) { + var paymentId = Restate.random().nextUUID().toString(); String payRef = - ctx.run("pay", String.class, () -> createRecurringPayment(req.creditCard(), paymentId)); + Restate.run("pay", String.class, () -> createRecurringPayment(req.creditCard(), paymentId)); for (String subscription : req.subscriptions()) { - ctx.run("add-" + subscription, () -> createSubscription(req.userId(), subscription, payRef)); + Restate.run( + "add-" + subscription, () -> createSubscription(req.userId(), subscription, payRef)); } } } diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/objects/UserSubscriptions.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/objects/UserSubscriptions.java index 62877beb..81cef0b9 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/objects/UserSubscriptions.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/objects/UserSubscriptions.java @@ -1,13 +1,11 @@ package my.example.objects; -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.SharedObjectContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.VirtualObject; import dev.restate.sdk.common.StateKey; import dev.restate.serde.TypeRef; -import java.time.Instant; import java.util.HashSet; import java.util.Set; @@ -18,20 +16,22 @@ public class UserSubscriptions { private static final StateKey LAST_UPDATED = StateKey.of("lastUpdated", String.class); @Handler - public void add(ObjectContext ctx, String subscription) { + public void add(String subscription) { + var state = Restate.state(); + // Get current subscriptions - Set subscriptions = ctx.get(SUBSCRIPTIONS).orElse(new HashSet<>()); + Set subscriptions = state.get(SUBSCRIPTIONS).orElse(new HashSet<>()); // Add new subscription subscriptions.add(subscription); - ctx.set(SUBSCRIPTIONS, subscriptions); + state.set(SUBSCRIPTIONS, subscriptions); // Update metrics - ctx.set(LAST_UPDATED, Instant.now().toString()); + state.set(LAST_UPDATED, Restate.instantNow().toString()); } @Shared - public Set getSubscriptions(SharedObjectContext ctx) { - return ctx.get(SUBSCRIPTIONS).orElse(Set.of()); + public Set getSubscriptions() { + return Restate.state().get(SUBSCRIPTIONS).orElse(Set.of()); } } diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/sagas/SubscriptionSaga.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/sagas/SubscriptionSaga.java index 4b98934e..ed280515 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/sagas/SubscriptionSaga.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/sagas/SubscriptionSaga.java @@ -1,6 +1,6 @@ package my.example.sagas; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.common.TerminalException; @@ -15,15 +15,15 @@ public class SubscriptionSaga { @Handler - public void add(Context ctx, SubscriptionRequest req) { + public void add(SubscriptionRequest req) { List compensations = new ArrayList<>(); try { - var paymentId = ctx.random().nextUUID().toString(); + var paymentId = Restate.random().nextUUID().toString(); compensations.add( - () -> ctx.run("undo-pay", () -> PaymentClient.removeRecurringPayment(paymentId))); + () -> Restate.run("undo-pay", () -> PaymentClient.removeRecurringPayment(paymentId))); String payRef = - ctx.run( + Restate.run( "pay", String.class, () -> PaymentClient.createRecurringPayment(req.creditCard(), paymentId)); @@ -31,10 +31,10 @@ public void add(Context ctx, SubscriptionRequest req) { for (String subscription : req.subscriptions()) { compensations.add( () -> - ctx.run( + Restate.run( "undo-" + subscription, () -> SubscriptionClient.removeSubscription(req.userId(), subscription))); - ctx.run( + Restate.run( "add-" + subscription, () -> SubscriptionClient.createSubscription(req.userId(), subscription, payRef)); } diff --git a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/timers/PaymentsWithTimeout.java b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/timers/PaymentsWithTimeout.java index a86bb65b..916d9552 100644 --- a/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/timers/PaymentsWithTimeout.java +++ b/java/tutorials/tour-of-orchestration-java/src/main/java/my/example/timers/PaymentsWithTimeout.java @@ -3,7 +3,7 @@ import static my.example.auxiliary.clients.PaymentClient.cancelPayment; import static my.example.auxiliary.clients.PaymentClient.initPayment; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import dev.restate.sdk.common.TimeoutException; @@ -16,23 +16,23 @@ public class PaymentsWithTimeout { @Handler - public PaymentResult process(Context ctx, PaymentRequest req) { - var confirmation = ctx.awakeable(PaymentResult.class); + public PaymentResult process(PaymentRequest req) { + var confirmation = Restate.awakeable(PaymentResult.class); - var paymentId = ctx.random().nextUUID().toString(); + var paymentId = Restate.random().nextUUID().toString(); String payRef = - ctx.run("pay", String.class, () -> initPayment(req, paymentId, confirmation.id())); + Restate.run("pay", String.class, () -> initPayment(req, paymentId, confirmation.id())); try { return confirmation.await(Duration.ofSeconds(30)); } catch (TimeoutException e) { - ctx.run("cancel-payment", () -> cancelPayment(payRef)); + Restate.run("cancel-payment", () -> cancelPayment(payRef)); return new PaymentResult(false, null, "Payment timeout"); } } @Handler - public void confirm(Context ctx, ConfirmationRequest confirmation) { - ctx.awakeableHandle(confirmation.id()).resolve(PaymentResult.class, confirmation.result()); + public void confirm(ConfirmationRequest confirmation) { + Restate.awakeableHandle(confirmation.id()).resolve(PaymentResult.class, confirmation.result()); } } diff --git a/java/tutorials/tour-of-workflows-java/build.gradle.kts b/java/tutorials/tour-of-workflows-java/build.gradle.kts index e3167f98..edb6bfa7 100644 --- a/java/tutorials/tour-of-workflows-java/build.gradle.kts +++ b/java/tutorials/tour-of-workflows-java/build.gradle.kts @@ -1,18 +1,16 @@ plugins { java application - id("com.diffplug.spotless") version "6.25.0" + id("com.diffplug.spotless") version "8.8.0" } repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - annotationProcessor("dev.restate:sdk-api-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-java-http:$restateVersion") implementation("dev.restate:client:${restateVersion}") @@ -23,12 +21,14 @@ dependencies { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(25)) } } // Set main class application { + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") if (project.hasProperty("mainClass")) { mainClass.set(project.property("mainClass") as String) } else { @@ -36,6 +36,12 @@ application { } } +tasks.named("test") { + useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} + spotless { java { googleJavaFormat() diff --git a/java/tutorials/tour-of-workflows-java/gradle/wrapper/gradle-wrapper.jar b/java/tutorials/tour-of-workflows-java/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/java/tutorials/tour-of-workflows-java/gradle/wrapper/gradle-wrapper.jar and b/java/tutorials/tour-of-workflows-java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/tutorials/tour-of-workflows-java/gradle/wrapper/gradle-wrapper.properties b/java/tutorials/tour-of-workflows-java/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/java/tutorials/tour-of-workflows-java/gradle/wrapper/gradle-wrapper.properties +++ b/java/tutorials/tour-of-workflows-java/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/tutorials/tour-of-workflows-java/gradlew b/java/tutorials/tour-of-workflows-java/gradlew index faf93008..249efbb0 100755 --- a/java/tutorials/tour-of-workflows-java/gradlew +++ b/java/tutorials/tour-of-workflows-java/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/java/tutorials/tour-of-workflows-java/gradlew.bat b/java/tutorials/tour-of-workflows-java/gradlew.bat index 9b42019c..8508ef68 100644 --- a/java/tutorials/tour-of-workflows-java/gradlew.bat +++ b/java/tutorials/tour-of-workflows-java/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/WorkflowSubmitter.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/WorkflowSubmitter.java index 51414d35..d9a87352 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/WorkflowSubmitter.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/WorkflowSubmitter.java @@ -2,7 +2,7 @@ import dev.restate.client.Client; import my.example.types.User; -import my.example.workflows.SignupWorkflowClient; +import my.example.workflows.SignupWorkflow; public class WorkflowSubmitter { @@ -12,7 +12,11 @@ public static void main(String[] args) throws Exception { Client restateClient = Client.connect("http://localhost:8080"); boolean result = - SignupWorkflowClient.fromClient(restateClient, "user-123").submit(user).attach().response(); + restateClient + .workflowHandle(SignupWorkflow.class, "user-123") + .send(SignupWorkflow::run, user) + .attach() + .response(); // System.out.println("Workflow result: " + result); diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/utils/UserService.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/utils/UserService.java index 93432dc1..3e85ea9a 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/utils/UserService.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/utils/UserService.java @@ -1,6 +1,6 @@ package my.example.utils; -import dev.restate.sdk.Context; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.Service; import my.example.types.CreateUserRequest; @@ -8,7 +8,8 @@ @Service public class UserService { @Handler - public Boolean createUser(Context ctx, CreateUserRequest req) { - return ctx.run("create user", Boolean.class, () -> Utils.createUser(req.userId(), req.user())); + public Boolean createUser(CreateUserRequest req) { + return Restate.run( + "create user", Boolean.class, () -> Utils.createUser(req.userId(), req.user())); } } diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithActivitiesWorkflow.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithActivitiesWorkflow.java index 907566b7..d6bb9f37 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithActivitiesWorkflow.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithActivitiesWorkflow.java @@ -3,31 +3,31 @@ import static my.example.utils.Utils.activateUser; import static my.example.utils.Utils.sendWelcomeEmail; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Workflow; import my.example.types.CreateUserRequest; import my.example.types.User; -import my.example.utils.UserServiceClient; +import my.example.utils.UserService; @Workflow public class SignupWithActivitiesWorkflow { @Workflow - public boolean run(WorkflowContext ctx, User user) { - String userId = ctx.key(); + public boolean run(User user) { + String userId = Restate.key(); // // Move user DB interaction to dedicated service boolean success = true; - UserServiceClient.fromContext(ctx).createUser(new CreateUserRequest(userId, user)).await(); + Restate.service(UserService.class).createUser(new CreateUserRequest(userId, user)); if (!success) { return false; } // Execute other steps inline - ctx.run("activate", () -> activateUser(userId)); - ctx.run("welcome", () -> sendWelcomeEmail(user)); + Restate.run("activate", () -> activateUser(userId)); + Restate.run("welcome", () -> sendWelcomeEmail(user)); // return true; diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithEventsWorkflow.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithEventsWorkflow.java index ccbeb6a1..9a3950fd 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithEventsWorkflow.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithEventsWorkflow.java @@ -2,8 +2,7 @@ import static my.example.utils.Utils.*; -import dev.restate.sdk.SharedWorkflowContext; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.common.DurablePromiseKey; @@ -16,25 +15,25 @@ public class SignupWithEventsWorkflow { DurablePromiseKey.of("user-created", String.class); @Workflow - public boolean run(WorkflowContext ctx, User user) { - String userId = ctx.key(); + public boolean run(User user) { + String userId = Restate.key(); - boolean success = ctx.run("create", Boolean.class, () -> createUser(userId, user)); + boolean success = Restate.run("create", Boolean.class, () -> createUser(userId, user)); if (!success) { - ctx.promiseHandle(USER_CREATED_PROMISE).reject("Creation failed."); + Restate.promiseHandle(USER_CREATED_PROMISE).reject("Creation failed."); return false; } - ctx.promiseHandle(USER_CREATED_PROMISE).resolve("User created."); + Restate.promiseHandle(USER_CREATED_PROMISE).resolve("User created."); - ctx.run("activate", () -> activateUser(userId)); - ctx.run("welcome", () -> sendWelcomeEmail(user)); + Restate.run("activate", () -> activateUser(userId)); + Restate.run("welcome", () -> sendWelcomeEmail(user)); return true; } @Shared - public String waitForUserCreation(SharedWorkflowContext ctx) { - return ctx.promise(USER_CREATED_PROMISE).future().await(); + public String waitForUserCreation() { + return Restate.promise(USER_CREATED_PROMISE).future().await(); } } diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithQueriesWorkflow.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithQueriesWorkflow.java index 32129177..c6ee61f4 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithQueriesWorkflow.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithQueriesWorkflow.java @@ -2,8 +2,7 @@ import static my.example.utils.Utils.*; -import dev.restate.sdk.SharedWorkflowContext; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.common.StateKey; @@ -17,27 +16,29 @@ public class SignupWithQueriesWorkflow { private static final StateKey STATUS = StateKey.of("status", String.class); @Workflow - public boolean run(WorkflowContext ctx, User user) { - String userId = ctx.key(); + public boolean run(User user) { + String userId = Restate.key(); + var state = Restate.state(); - ctx.set(USER, user); - boolean success = ctx.run("create", Boolean.class, () -> createUser(userId, user)); + state.set(USER, user); + boolean success = Restate.run("create", Boolean.class, () -> createUser(userId, user)); if (!success) { - ctx.set(STATUS, "failed"); + state.set(STATUS, "failed"); return false; } - ctx.set(STATUS, "created"); + state.set(STATUS, "created"); - ctx.run("activate", () -> activateUser(userId)); - ctx.run("welcome", () -> sendWelcomeEmail(user)); + Restate.run("activate", () -> activateUser(userId)); + Restate.run("welcome", () -> sendWelcomeEmail(user)); return true; } @Shared - public StatusResponse getStatus(SharedWorkflowContext ctx) { - String status = ctx.get(STATUS).orElse("unknown"); - User user = ctx.get(USER).orElse(null); + public StatusResponse getStatus() { + var state = Restate.state(); + String status = state.get(STATUS).orElse("unknown"); + User user = state.get(USER).orElse(null); return new StatusResponse(status, user); } } diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithRetriesWorkflow.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithRetriesWorkflow.java index 12444986..2735f15c 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithRetriesWorkflow.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithRetriesWorkflow.java @@ -2,7 +2,7 @@ import static my.example.utils.Utils.*; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.common.RetryPolicy; import dev.restate.sdk.common.TerminalException; @@ -13,15 +13,15 @@ public class SignupWithRetriesWorkflow { @Workflow - public boolean run(WorkflowContext ctx, User user) { - String userId = ctx.key(); + public boolean run(User user) { + String userId = Restate.key(); - boolean success = ctx.run(Boolean.class, () -> createUser(userId, user)); + boolean success = Restate.run("create", Boolean.class, () -> createUser(userId, user)); if (!success) { return false; } - ctx.run("activate", () -> activateUser(userId)); + Restate.run("activate", () -> activateUser(userId)); // Configure retry policy // @@ -34,7 +34,7 @@ public boolean run(WorkflowContext ctx, User user) { .setMaxAttempts(3) .setMaxDuration(Duration.ofSeconds(30)); - ctx.run("welcome", myRunRetryPolicy, () -> sendWelcomeEmail(user)); + Restate.run("welcome", myRunRetryPolicy, () -> sendWelcomeEmail(user)); } catch (TerminalException error) { // This gets hit on retry exhaustion with a terminal error // Log and continue; without letting the workflow fail diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithSagasWorkflow.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithSagasWorkflow.java index 286ccd1c..4a412e9b 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithSagasWorkflow.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithSagasWorkflow.java @@ -2,7 +2,7 @@ import static my.example.utils.Utils.*; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.common.TerminalException; import java.util.ArrayList; @@ -14,19 +14,19 @@ public class SignupWithSagasWorkflow { @Workflow - public boolean run(WorkflowContext ctx, User user) { - String userId = ctx.key(); + public boolean run(User user) { + String userId = Restate.key(); List compensations = new ArrayList<>(); try { - compensations.add(() -> ctx.run("delete", () -> deleteUser(userId))); - ctx.run("create", () -> createUser(userId, user)); + compensations.add(() -> Restate.run("delete", () -> deleteUser(userId))); + Restate.run("create", () -> createUser(userId, user)); - compensations.add(() -> ctx.run("deactivate", () -> deactivateUser(userId))); - ctx.run("activate", () -> activateUser(userId)); + compensations.add(() -> Restate.run("deactivate", () -> deactivateUser(userId))); + Restate.run("activate", () -> activateUser(userId)); - compensations.add(() -> ctx.run("unsubscribe", () -> cancelSubscription(user))); - ctx.run("subscribe", () -> subscribeToPaidPlan(user)); + compensations.add(() -> Restate.run("unsubscribe", () -> cancelSubscription(user))); + Restate.run("subscribe", () -> subscribeToPaidPlan(user)); } catch (TerminalException e) { // Run compensations in reverse order diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithSignalsWorkflow.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithSignalsWorkflow.java index 6c7bf673..334158c2 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithSignalsWorkflow.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithSignalsWorkflow.java @@ -2,8 +2,7 @@ import static my.example.utils.Utils.*; -import dev.restate.sdk.SharedWorkflowContext; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.common.DurablePromiseKey; @@ -17,20 +16,20 @@ public class SignupWithSignalsWorkflow { DurablePromiseKey.of("email-verified", String.class); @Workflow - public boolean run(WorkflowContext ctx, User user) { - String userId = ctx.key(); + public boolean run(User user) { + String userId = Restate.key(); // Generate verification secret and send email - String secret = ctx.random().nextUUID().toString(); - ctx.run("verify", () -> sendVerificationEmail(userId, user, secret)); + String secret = Restate.random().nextUUID().toString(); + Restate.run("verify", () -> sendVerificationEmail(userId, user, secret)); // Wait for user to click verification link - String clickedSecret = ctx.promise(EMAIL_VERIFIED_PROMISE).future().await(); + String clickedSecret = Restate.promise(EMAIL_VERIFIED_PROMISE).future().await(); return secret.equals(clickedSecret); } @Shared - public void verifyEmail(SharedWorkflowContext ctx, VerifyEmailRequest req) { - ctx.promiseHandle(EMAIL_VERIFIED_PROMISE).resolve(req.secret()); + public void verifyEmail(VerifyEmailRequest req) { + Restate.promiseHandle(EMAIL_VERIFIED_PROMISE).resolve(req.secret()); } } diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithTimersWorkflow.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithTimersWorkflow.java index 430160f3..ffd84b23 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithTimersWorkflow.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWithTimersWorkflow.java @@ -2,9 +2,8 @@ import static my.example.utils.Utils.*; +import dev.restate.sdk.Restate; import dev.restate.sdk.Select; -import dev.restate.sdk.SharedWorkflowContext; -import dev.restate.sdk.WorkflowContext; import dev.restate.sdk.annotation.Shared; import dev.restate.sdk.annotation.Workflow; import dev.restate.sdk.common.DurablePromiseKey; @@ -20,17 +19,17 @@ public class SignupWithTimersWorkflow { DurablePromiseKey.of("email-verified", String.class); @Workflow - public boolean run(WorkflowContext ctx, User user) { - String userId = ctx.key(); + public boolean run(User user) { + String userId = Restate.key(); - var confirmationFuture = ctx.promise(EMAIL_VERIFIED_PROMISE).future(); - var secret = ctx.random().nextUUID().toString(); - ctx.run("verify", () -> sendVerificationEmail(userId, user, secret)); + var confirmationFuture = Restate.promise(EMAIL_VERIFIED_PROMISE).future(); + var secret = Restate.random().nextUUID().toString(); + Restate.run("verify", () -> sendVerificationEmail(userId, user, secret)); - var verificationTimeout = ctx.timer(Duration.ofDays(1)); + var verificationTimeout = Restate.timer(Duration.ofDays(1)); while (true) { - var reminderTimer = ctx.timer(Duration.ofSeconds(10)); + var reminderTimer = Restate.timer(Duration.ofSeconds(10)); var selected = Select.select() @@ -44,7 +43,7 @@ public boolean run(WorkflowContext ctx, User user) { var clickedSecret = confirmationFuture.await(); return secret.equals(clickedSecret); case "reminder": - ctx.run("send reminder", () -> sendReminderEmail(userId, user, secret)); + Restate.run("send reminder", () -> sendReminderEmail(userId, user, secret)); break; case "timeout": throw new TerminalException("Verification timed out"); @@ -53,7 +52,7 @@ public boolean run(WorkflowContext ctx, User user) { } @Shared - public void verifyEmail(SharedWorkflowContext ctx, VerifyEmailRequest req) { - ctx.promiseHandle(EMAIL_VERIFIED_PROMISE).resolve(req.secret()); + public void verifyEmail(VerifyEmailRequest req) { + Restate.promiseHandle(EMAIL_VERIFIED_PROMISE).resolve(req.secret()); } } diff --git a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWorkflow.java b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWorkflow.java index cf711ccc..1064cf52 100644 --- a/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWorkflow.java +++ b/java/tutorials/tour-of-workflows-java/src/main/java/my/example/workflows/SignupWorkflow.java @@ -2,7 +2,7 @@ import static my.example.utils.Utils.*; -import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.Restate; import dev.restate.sdk.annotation.Workflow; import my.example.types.User; @@ -10,18 +10,18 @@ public class SignupWorkflow { @Workflow - public boolean run(WorkflowContext ctx, User user) { - String userId = ctx.key(); // workflow ID = user ID + public boolean run(User user) { + String userId = Restate.key(); // workflow ID = user ID // Write to database - boolean success = ctx.run("create", Boolean.class, () -> createUser(userId, user)); + boolean success = Restate.run("create", Boolean.class, () -> createUser(userId, user)); if (!success) { return false; } // Call APIs - ctx.run("activate", () -> activateUser(userId)); - ctx.run("welcome", () -> sendWelcomeEmail(user)); + Restate.run("activate", () -> activateUser(userId)); + Restate.run("welcome", () -> sendWelcomeEmail(user)); return true; } diff --git a/kotlin/basics/build.gradle.kts b/kotlin/basics/build.gradle.kts index 02663b4d..8ef48800 100644 --- a/kotlin/basics/build.gradle.kts +++ b/kotlin/basics/build.gradle.kts @@ -1,27 +1,31 @@ plugins { application - kotlin("jvm") version "2.2.10" - kotlin("plugin.serialization") version "2.2.10" - id("com.google.devtools.ksp") version "2.2.10-2.0.2" + kotlin("jvm") version "2.4.0" + kotlin("plugin.serialization") version "2.4.0" + kotlin("plugin.allopen") version "2.4.0" } repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - // Annotation processor - ksp("dev.restate:sdk-api-kotlin-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-kotlin-http:$restateVersion") implementation("org.apache.logging.log4j:log4j-api:2.24.1") } kotlin { - jvmToolchain(21) + jvmToolchain(25) +} + +allOpen { + // Restate proxy clients need non-final classes. + annotation("dev.restate.sdk.annotation.Service") + annotation("dev.restate.sdk.annotation.VirtualObject") + annotation("dev.restate.sdk.annotation.Workflow") } // Set main class @@ -31,4 +35,6 @@ application { } else { mainClass.set("durable_execution.RoleUpdateServiceKt") } + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } diff --git a/kotlin/basics/gradle/wrapper/gradle-wrapper.jar b/kotlin/basics/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/kotlin/basics/gradle/wrapper/gradle-wrapper.jar and b/kotlin/basics/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/basics/gradle/wrapper/gradle-wrapper.properties b/kotlin/basics/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/kotlin/basics/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/basics/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin/basics/gradlew b/kotlin/basics/gradlew index faf93008..249efbb0 100755 --- a/kotlin/basics/gradlew +++ b/kotlin/basics/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/kotlin/basics/gradlew.bat b/kotlin/basics/gradlew.bat index 9b42019c..8508ef68 100644 --- a/kotlin/basics/gradlew.bat +++ b/kotlin/basics/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/kotlin/basics/src/main/kotlin/building_blocks/MyService.kt b/kotlin/basics/src/main/kotlin/building_blocks/MyService.kt index 4ec245cb..810cf3dc 100644 --- a/kotlin/basics/src/main/kotlin/building_blocks/MyService.kt +++ b/kotlin/basics/src/main/kotlin/building_blocks/MyService.kt @@ -6,7 +6,6 @@ import dev.restate.sdk.annotation.Service import dev.restate.sdk.http.vertx.RestateHttpServer import dev.restate.sdk.kotlin.endpoint.endpoint import utils.SubscriptionService -import utils.SubscriptionServiceClient import utils.chargeBankAccount import java.util.UUID import kotlin.time.Duration.Companion.days @@ -31,43 +30,50 @@ class MyService { // This handler can be called over HTTP at http://restate:8080/MyService/handlerName // Use the context to access Restate's durable building blocks @Handler - suspend fun run(ctx: Context) { + suspend fun run() { // 1. IDEMPOTENCY: Add an idempotency key to the header of your requests // Restate deduplicates calls automatically. Nothing to do here. // 2. DURABLE RPC: Call other services without manual retry and deduplication logic // Restate persists all requests and ensures execution till completion - val result = SubscriptionServiceClient.fromContext(ctx, "my-sub-123").create("my-request").await() + val result = toVirtualObject("my-sub-123") + .request { create("my-request") } + .call() + .await() // 3. DURABLE MESSAGING: send (delayed) messages to other services without deploying a message broker // Restate persists the timers and triggers execution - SubscriptionServiceClient.fromContext(ctx, "my-sub-123").send().create("my-request") + toVirtualObject("my-sub-123") + .request { create("my-request") } + .send() // 4. DURABLE PROMISES: tracked by Restate, can be moved between processes and survive failures // Awakeables: block the workflow until notified by another handler - val awakeable = ctx.awakeable() + val awakeable = awakeable() // Wait on the promise // If the process crashes while waiting, Restate will recover the promise somewhere else val greeting = awakeable.await() // Another process can resolve the awakeable via its ID - ctx.awakeableHandle(awakeable.id).resolve("hello") + awakeableHandle(awakeable.id).resolve("hello") // 5. DURABLE TIMERS: sleep or wait for a timeout, tracked by Restate and recoverable // When this runs on FaaS, the handler suspends and the timer is tracked by Restate // Example of durable recoverable sleep // If the service crashes two seconds later, Restate will invoke it after another 3 seconds - ctx.sleep(5.seconds) + sleep(5.seconds) // Example of waiting on a promise (call/awakeable/...) or a timeout awakeable.await(5.seconds) // Example of scheduling a handler for later on - SubscriptionServiceClient.fromContext(ctx, "my-sub-123").send().cancel(1.days) + toVirtualObject("my-sub-123") + .request { cancel() } + .send(1.days) // 7. PERSIST RESULTS: avoid re-execution of actions on retries // Use this for non-deterministic actions or interaction with APIs, DBs, ... // For example, generate idempotency keys that are stable across retries // Then use these to call other APIs and let them deduplicate val paymentDeduplicationID = UUID.randomUUID().toString() - ctx.runBlock { chargeBankAccount(paymentDeduplicationID, 100) } + runBlock { chargeBankAccount(paymentDeduplicationID, 100) } } } diff --git a/kotlin/basics/src/main/kotlin/durable_execution/SubscriptionService.kt b/kotlin/basics/src/main/kotlin/durable_execution/SubscriptionService.kt index 5d2804e6..99c1463b 100644 --- a/kotlin/basics/src/main/kotlin/durable_execution/SubscriptionService.kt +++ b/kotlin/basics/src/main/kotlin/durable_execution/SubscriptionService.kt @@ -33,19 +33,19 @@ import utils.createSubscription class SubscriptionService { @Handler - suspend fun add(ctx: Context, req: SubscriptionRequest) { - // Restate persists the result of all `ctx` actions and recovers them after failures + suspend fun add(req: SubscriptionRequest) { + // Restate persists the result of all durable actions and recovers them after failures // For example, generate a stable idempotency key: - val paymentId = ctx.random().nextUUID().toString() + val paymentId = random().nextUUID().toString() - // ctx.run persists results of successful actions and skips execution on retries + // runBlock persists results of successful actions and skips execution on retries // Failed actions (timeouts, API downtime, etc.) get retried - val payRef = ctx.runBlock { + val payRef = runBlock { createRecurringPayment(req.creditCard, paymentId) } for (subscription in req.subscriptions) { - ctx.runBlock { + runBlock { createSubscription(req.userId, subscription, payRef) } } diff --git a/kotlin/basics/src/main/kotlin/utils/SubscriptionService.kt b/kotlin/basics/src/main/kotlin/utils/SubscriptionService.kt index 335a4693..837599d1 100644 --- a/kotlin/basics/src/main/kotlin/utils/SubscriptionService.kt +++ b/kotlin/basics/src/main/kotlin/utils/SubscriptionService.kt @@ -2,16 +2,16 @@ package utils import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.VirtualObject -import dev.restate.sdk.kotlin.ObjectContext +import dev.restate.sdk.kotlin.* @VirtualObject class SubscriptionService { @Handler - fun create(ctx: ObjectContext, userId: String): String = + fun create(userId: String): String = "SUCCESS" @Handler - fun cancel(ctx: ObjectContext) = - println("Cancelling all subscriptions for user ${ctx.key()}") -} \ No newline at end of file + suspend fun cancel() = + println("Cancelling all subscriptions for user ${objectKey()}") +} diff --git a/kotlin/basics/src/main/kotlin/virtual_objects/GreeterObject.kt b/kotlin/basics/src/main/kotlin/virtual_objects/GreeterObject.kt index 4303ebbe..c88f4317 100644 --- a/kotlin/basics/src/main/kotlin/virtual_objects/GreeterObject.kt +++ b/kotlin/basics/src/main/kotlin/virtual_objects/GreeterObject.kt @@ -28,25 +28,27 @@ class GreeterObject { } @Handler - suspend fun greet(ctx: ObjectContext, greeting: String): String { + suspend fun greet(greeting: String): String { // Access the state attached to this object (this 'name') // State access and updates are exclusive and consistent with the invocations - val count = ctx.get(COUNT) ?: 0 + val state = state() + val count = state.get(COUNT) ?: 0 val newCount = count + 1 - ctx.set(COUNT, newCount) + state.set(COUNT, newCount) - return "$greeting ${ctx.key()}, for the $newCount-th time" + return "$greeting ${objectKey()}, for the $newCount-th time" } @Handler - suspend fun ungreet(ctx: ObjectContext): String { - val count = ctx.get(COUNT) ?: 0 + suspend fun ungreet(): String { + val state = state() + val count = state.get(COUNT) ?: 0 if (count > 0) { val newCount = count - 1 - ctx.set(COUNT, newCount) + state.set(COUNT, newCount) } - return "Dear ${ctx.key()}, taking one greeting back: $count" + return "Dear ${objectKey()}, taking one greeting back: $count" } } diff --git a/kotlin/basics/src/main/kotlin/workflows/SignupWorkflow.kt b/kotlin/basics/src/main/kotlin/workflows/SignupWorkflow.kt index d64a1ea0..8357083c 100644 --- a/kotlin/basics/src/main/kotlin/workflows/SignupWorkflow.kt +++ b/kotlin/basics/src/main/kotlin/workflows/SignupWorkflow.kt @@ -26,21 +26,21 @@ class SignupWorkflow { } @Workflow - suspend fun run(ctx: WorkflowContext, user: User): Boolean { + suspend fun run(user: User): Boolean { // workflow ID = user ID; workflow runs once per user - val userId = ctx.key() + val userId = workflowKey() // Durably executed action; write to other system - ctx.runBlock { createUserEntry(user) } + runBlock { createUserEntry(user) } // Sent user email with verification link - val secret = ctx.random().nextUUID().toString() - ctx.runBlock { sendEmailWithLink(userId, user, secret) } + val secret = random().nextUUID().toString() + runBlock { sendEmailWithLink(userId, user, secret) } // Wait until user clicked email verification link // Promise gets resolved or rejected by the other handlers val clickSecret: String = - ctx.promise(LINK_CLICKED) + promise(LINK_CLICKED) .future() .await() @@ -49,9 +49,9 @@ class SignupWorkflow { // --- Other handlers interact with the workflow via queries and signals --- @Shared - suspend fun click(ctx: SharedWorkflowContext, secret: String) { + suspend fun click(secret: String) { // Send data to the workflow via a durable promise - ctx.promiseHandle(LINK_CLICKED).resolve(secret) + promiseHandle(LINK_CLICKED).resolve(secret) } } diff --git a/kotlin/end-to-end-applications/food-ordering/README.md b/kotlin/end-to-end-applications/food-ordering/README.md index 38a48d36..bd4e0d09 100644 --- a/kotlin/end-to-end-applications/food-ordering/README.md +++ b/kotlin/end-to-end-applications/food-ordering/README.md @@ -86,7 +86,7 @@ The flow of an incoming order is as follows: 2. It then calls the `OrderStatusService` to create a new order in the system. The `OrderStatusService` is a virtual object which tracks the status of each order by storing it in Restate's key-value store. 3. The order workflow then triggers the payment by calling a third-party payment provider (implemented as a stub in this example). To do this, the order workflow first generates a stable idempotency token provided by the SDK, and then uses this to call the payment provider. The payment provider can deduplicate retries via the idempotency key. 4. The workflow then sets the order status to `SCHEDULED` and sets a timer to continue processing after the delivery delay has passed. For example, if a customer ordered food for later in the day, the order will be scheduled for preparation at the requested time. If any failures occur during the sleep, Restate makes sure that the workflow will still wake up on time. - 5. Once the timer fires, the order workflow creates an awakeable and sends a request to the restaurant point-of-sales system to start the preparation. This is done via an HTTP request wrapped in `ctx.runBlock` to log the result in Restate. The status of the order is set to `IN_PREPARATION`. The restaurant will use the awakeable callback to signal when the prepration is done. Once this happens, the order workflow will continue and set the order status to `SCHEDULING_DELIVERY`. + 5. Once the timer fires, the order workflow creates an awakeable and sends a request to the restaurant point-of-sales system to start the preparation. This is done via an HTTP request wrapped in `runBlock` to log the result in Restate. The status of the order is set to `IN_PREPARATION`. The restaurant will use the awakeable callback to signal when the prepration is done. Once this happens, the order workflow will continue and set the order status to `SCHEDULING_DELIVERY`. 6. Finally, the order workflow calls the delivery manager (`DeliveryManager.kt`) to schedule the delivery of the order (see description below). It does this by using an awakeable, that the delivery manager will use to signal when the delivery is done. Once the delivery is done, the order workflow will set the order status to `DELIVERED`. ### The delivery workflow diff --git a/kotlin/end-to-end-applications/food-ordering/app/build.gradle.kts b/kotlin/end-to-end-applications/food-ordering/app/build.gradle.kts index d380b831..74037be9 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/build.gradle.kts +++ b/kotlin/end-to-end-applications/food-ordering/app/build.gradle.kts @@ -1,13 +1,17 @@ plugins { application - kotlin("jvm") version "2.2.10" - kotlin("plugin.serialization") version "2.2.10" - id("com.diffplug.spotless") version "6.25.0" + kotlin("jvm") version "2.4.0" + kotlin("plugin.serialization") version "2.4.0" + // Restate proxy clients need non-final classes; the all-open plugin opens + // classes annotated with the Restate annotations (see allOpen block below). + kotlin("plugin.allopen") version "2.4.0" + id("com.diffplug.spotless") version "8.8.0" } subprojects { apply(plugin = "kotlin") apply(plugin = "org.jetbrains.kotlin.plugin.serialization") + apply(plugin = "org.jetbrains.kotlin.plugin.allopen") apply(plugin = "com.diffplug.spotless") dependencies { @@ -15,7 +19,13 @@ subprojects { } kotlin { - jvmToolchain(17) + jvmToolchain(25) + } + + allOpen { + annotation("dev.restate.sdk.annotation.Service") + annotation("dev.restate.sdk.annotation.VirtualObject") + annotation("dev.restate.sdk.annotation.Workflow") } // Code formatting tool @@ -30,5 +40,7 @@ subprojects { tasks.withType { useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } } \ No newline at end of file diff --git a/kotlin/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.jar b/kotlin/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/kotlin/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.jar and b/kotlin/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.properties b/kotlin/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/end-to-end-applications/food-ordering/app/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin/end-to-end-applications/food-ordering/app/gradlew b/kotlin/end-to-end-applications/food-ordering/app/gradlew index faf93008..249efbb0 100755 --- a/kotlin/end-to-end-applications/food-ordering/app/gradlew +++ b/kotlin/end-to-end-applications/food-ordering/app/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/kotlin/end-to-end-applications/food-ordering/app/gradlew.bat b/kotlin/end-to-end-applications/food-ordering/app/gradlew.bat index 9b42019c..8508ef68 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/gradlew.bat +++ b/kotlin/end-to-end-applications/food-ordering/app/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/build.gradle.kts b/kotlin/end-to-end-applications/food-ordering/app/restate-app/build.gradle.kts index cf64f7db..3b9d7c8d 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/build.gradle.kts +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/build.gradle.kts @@ -1,16 +1,16 @@ plugins { application - id("com.google.cloud.tools.jib") version "3.4.0" - id("com.google.devtools.ksp") version "2.2.10-2.0.2" + id("com.google.cloud.tools.jib") version "3.5.3" } -repositories { mavenCentral() } +repositories { + mavenCentral() +} -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { // Restate SDK - ksp("dev.restate:sdk-api-kotlin-gen:$restateVersion") implementation("dev.restate:sdk-kotlin-http:$restateVersion") // Kafka @@ -18,7 +18,13 @@ dependencies { } // Set main class -application { mainClass.set("dev.restate.sdk.examples.AppMainKt") } +application { + mainClass.set("dev.restate.sdk.examples.AppMainKt") + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, + // --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = + listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} jib { to.image = "restate-app:0.0.1" diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/AppMain.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/AppMain.kt index 7bce9cb0..e905ae6c 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/AppMain.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/AppMain.kt @@ -15,7 +15,8 @@ import dev.restate.sdk.http.vertx.RestateHttpServer import dev.restate.sdk.kotlin.endpoint.endpoint fun main() { - RestateHttpServer.listen(endpoint { + RestateHttpServer.listen( + endpoint { bind(OrderWorkflow()) bind(OrderETAService()) bind(DriverDeliveryMatcher()) @@ -23,5 +24,6 @@ fun main() { // external mobile app on driver's phone bind(DriverMobileAppSimulator()) - }) + } + ) } diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/DriverDeliveryMatcher.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/DriverDeliveryMatcher.kt index 16283701..34a23a17 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/DriverDeliveryMatcher.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/DriverDeliveryMatcher.kt @@ -35,23 +35,24 @@ class DriverDeliveryMatcher { * in line. If no pending deliveries, driver is added to the available driver pool */ @Handler - suspend fun setDriverAvailable(ctx: ObjectContext, driverId: String) { - val pendingDeliveries = ctx.get(PENDING_DELIVERIES) ?: mutableListOf() + suspend fun setDriverAvailable(driverId: String) { + val state = state() + val pendingDeliveries = state.get(PENDING_DELIVERIES) ?: mutableListOf() // If there is a pending delivery, assign it to the driver val nextDelivery = pendingDeliveries.removeFirstOrNull() if (nextDelivery != null) { // Update the queue in state. Delivery was removed. - ctx.set(PENDING_DELIVERIES, pendingDeliveries) + state.set(PENDING_DELIVERIES, pendingDeliveries) // Notify that delivery is ongoing - ctx.awakeableHandle(nextDelivery).resolve(driverId) + awakeableHandle(nextDelivery).resolve(driverId) return } // Otherwise remember driver as available - val availableDrivers = ctx.get(AVAILABLE_DRIVERS) ?: mutableListOf() + val availableDrivers = state.get(AVAILABLE_DRIVERS) ?: mutableListOf() availableDrivers.add(driverId) - ctx.set(AVAILABLE_DRIVERS, availableDrivers) + state.set(AVAILABLE_DRIVERS, availableDrivers) } /** @@ -59,22 +60,23 @@ class DriverDeliveryMatcher { * available. If no available drivers, the delivery is added to the pending deliveries queue */ @Handler - suspend fun requestDriverForDelivery(ctx: ObjectContext, deliveryCallbackId: String) { - val availableDrivers = ctx.get(AVAILABLE_DRIVERS) ?: mutableListOf() + suspend fun requestDriverForDelivery(deliveryCallbackId: String) { + val state = state() + val availableDrivers = state.get(AVAILABLE_DRIVERS) ?: mutableListOf() // If a driver is available, assign the delivery right away val nextAvailableDriver = availableDrivers.removeFirstOrNull() if (nextAvailableDriver != null) { // Remove driver from the pool - ctx.set(AVAILABLE_DRIVERS, availableDrivers) + state.set(AVAILABLE_DRIVERS, availableDrivers) // Notify that delivery is ongoing - ctx.awakeableHandle(deliveryCallbackId).resolve(nextAvailableDriver) + awakeableHandle(deliveryCallbackId).resolve(nextAvailableDriver) return } // otherwise store the delivery request until a new driver becomes available - val pendingDeliveries = ctx.get(PENDING_DELIVERIES) ?: mutableListOf() + val pendingDeliveries = state.get(PENDING_DELIVERIES) ?: mutableListOf() pendingDeliveries.add(deliveryCallbackId) - ctx.set(PENDING_DELIVERIES, pendingDeliveries) + state.set(PENDING_DELIVERIES, pendingDeliveries) } } diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/DriverDigitalTwin.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/DriverDigitalTwin.kt index ef4cff52..0830a182 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/DriverDigitalTwin.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/DriverDigitalTwin.kt @@ -13,8 +13,8 @@ package dev.restate.sdk.examples import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.Shared import dev.restate.sdk.annotation.VirtualObject -import dev.restate.sdk.kotlin.* import dev.restate.sdk.common.TerminalException +import dev.restate.sdk.kotlin.* /** * Digital twin for the driver. Represents a driver and his status, assigned delivery, and location. @@ -39,11 +39,12 @@ class DriverDigitalTwin { * (DriverMobileAppSimulator) calls this method. */ @Handler - suspend fun setDriverAvailable(ctx: ObjectContext, region: String) { - expectStatus(ctx, DriverStatus.IDLE) + suspend fun setDriverAvailable(region: String) { + expectStatus(DriverStatus.IDLE) - ctx.set(DRIVER_STATUS, DriverStatus.WAITING_FOR_WORK) - DriverDeliveryMatcherClient.fromContext(ctx, region).send().setDriverAvailable(ctx.key()) + val driverId = objectKey() + state().set(DRIVER_STATUS, DriverStatus.WAITING_FOR_WORK) + toVirtualObject(region).request { setDriverAvailable(driverId) }.send() } /** @@ -52,29 +53,37 @@ class DriverDigitalTwin { * location. */ @Handler - suspend fun assignDeliveryJob(ctx: ObjectContext, request: AssignDeliveryRequest) { - expectStatus(ctx, DriverStatus.WAITING_FOR_WORK) + suspend fun assignDeliveryJob(request: AssignDeliveryRequest) { + expectStatus(DriverStatus.WAITING_FOR_WORK) // Update the status and assigned delivery information of the driver - ctx.set(DRIVER_STATUS, DriverStatus.DELIVERING) - ctx.set( + val state = state() + state.set(DRIVER_STATUS, DriverStatus.DELIVERING) + state.set( ASSIGNED_DELIVERY, AssignedDelivery( - ctx.key(), + objectKey(), request.orderId, request.restaurantId, request.restaurantLocation, - request.customerLocation)) + request.customerLocation, + ), + ) // Initialize the ETA service - OrderETAServiceClient.fromContext(ctx, request.orderId) + toVirtualObject(request.orderId) + .request { + notifyDeliveryLocations( + DeliveryLocations(request.restaurantLocation, request.customerLocation) + ) + } .send() - .notifyDeliveryLocations( - DeliveryLocations(request.restaurantLocation, request.customerLocation)) // Notify current location to the delivery service - ctx.get(DRIVER_LOCATION)?.let { - OrderETAServiceClient.fromContext(ctx, request.orderId).send().notifyDriverLocationUpdate(it) + state.get(DRIVER_LOCATION)?.let { location -> + toVirtualObject(request.orderId) + .request { notifyDriverLocationUpdate(location) } + .send() } } @@ -82,51 +91,64 @@ class DriverDigitalTwin { * Gets called by the driver's mobile app when he has picked up the delivery from the restaurant. */ @Handler - suspend fun notifyDeliveryPickup(ctx: ObjectContext) { - expectStatus(ctx, DriverStatus.DELIVERING) + suspend fun notifyDeliveryPickup() { + expectStatus(DriverStatus.DELIVERING) // Retrieve the ongoing delivery and update its status + val state = state() val currentDelivery = - ctx.get(ASSIGNED_DELIVERY) + state.get(ASSIGNED_DELIVERY) ?: throw TerminalException( - "Driver is in status DELIVERING but there is no current delivery set.") + "Driver is in status DELIVERING but there is no current delivery set." + ) currentDelivery.notifyPickup() - ctx.set(ASSIGNED_DELIVERY, currentDelivery) + state.set(ASSIGNED_DELIVERY, currentDelivery) // Update the status of the delivery in the delivery manager - OrderWorkflowClient.fromContext(ctx, currentDelivery.orderId).send().notifyDeliveryPickup() - OrderETAServiceClient.fromContext(ctx, currentDelivery.orderId).send().notifyDeliveryPickup() + toVirtualObject(currentDelivery.orderId) + .request { notifyDeliveryPickup() } + .send() + toVirtualObject(currentDelivery.orderId) + .request { notifyDeliveryPickup() } + .send() } /** Gets called by the driver's mobile app when he has delivered the order to the customer. */ @Handler - suspend fun notifyDeliveryDelivered(ctx: ObjectContext) { - expectStatus(ctx, DriverStatus.DELIVERING) + suspend fun notifyDeliveryDelivered() { + expectStatus(DriverStatus.DELIVERING) // Retrieve the ongoing delivery + val state = state() val assignedDelivery = - ctx.get(ASSIGNED_DELIVERY) + state.get(ASSIGNED_DELIVERY) ?: throw TerminalException( - "Driver is in status DELIVERING but there is no current delivery set.") + "Driver is in status DELIVERING but there is no current delivery set." + ) // Clean up the state - ctx.clear(ASSIGNED_DELIVERY) + state.clear(ASSIGNED_DELIVERY) // Notify the delivery service that the delivery was delivered - OrderWorkflowClient.fromContext(ctx, assignedDelivery.orderId).send().notifyDeliveryDelivered() + toVirtualObject(assignedDelivery.orderId) + .request { notifyDeliveryDelivered() } + .send() // Update the status of the driver to idle - ctx.set(DRIVER_STATUS, DriverStatus.IDLE) + state.set(DRIVER_STATUS, DriverStatus.IDLE) } /** Gets called by the driver's mobile app when he has moved to a new location. */ @Handler - suspend fun handleDriverLocationUpdateEvent(ctx: ObjectContext, location: Location) { + suspend fun handleDriverLocationUpdateEvent(location: Location) { // Update the location of the driver - ctx.set(DRIVER_LOCATION, location) + val state = state() + state.set(DRIVER_LOCATION, location) // Update the location of the delivery, if there is one - ctx.get(ASSIGNED_DELIVERY)?.let { - OrderETAServiceClient.fromContext(ctx, it.orderId).send().notifyDriverLocationUpdate(location) + state.get(ASSIGNED_DELIVERY)?.let { delivery -> + toVirtualObject(delivery.orderId) + .request { notifyDriverLocationUpdate(location) } + .send() } } @@ -136,20 +158,21 @@ class DriverDigitalTwin { * got assigned to him. */ @Shared - suspend fun getAssignedDelivery(ctx: SharedObjectContext): GetAssignedDeliveryResult { - return GetAssignedDeliveryResult(ctx.get(ASSIGNED_DELIVERY)) + suspend fun getAssignedDelivery(): GetAssignedDeliveryResult { + return GetAssignedDeliveryResult(state().get(ASSIGNED_DELIVERY)) } // Utility function to check if the driver is in the expected state // If the driver is in a different state, a terminal exception is thrown that stops any retries // from taking place. // Is only called from inside the driver service. - private suspend fun expectStatus(ctx: ObjectContext, expectedStatus: DriverStatus) { - val currentStatus = ctx.get(DRIVER_STATUS) ?: DriverStatus.IDLE + private suspend fun expectStatus(expectedStatus: DriverStatus) { + val currentStatus = state().get(DRIVER_STATUS) ?: DriverStatus.IDLE if (currentStatus != expectedStatus) { throw TerminalException( - "Driver status wrong. Expected $expectedStatus but was $currentStatus") + "Driver status wrong. Expected $expectedStatus but was $currentStatus" + ) } } } diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/OrderETAService.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/OrderETAService.kt index 16a239c7..0f5e0c8d 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/OrderETAService.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/OrderETAService.kt @@ -13,9 +13,9 @@ package dev.restate.sdk.examples import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.Shared import dev.restate.sdk.annotation.VirtualObject +import dev.restate.sdk.common.TerminalException import dev.restate.sdk.examples.utils.GeoUtils import dev.restate.sdk.kotlin.* -import dev.restate.sdk.common.TerminalException /** Virtual object tracking the ETA, keyed by order-id */ @VirtualObject @@ -28,13 +28,13 @@ class OrderETAService { /** Gets called by the webUI frontend to display the status of an order. */ @Shared - suspend fun get(ctx: SharedObjectContext): Long { - return ctx.get(ORDER_ETA) ?: -1L + suspend fun get(): Long { + return state().get(ORDER_ETA) ?: -1L } @Handler - suspend fun notifyDeliveryLocations(ctx: ObjectContext, locations: DeliveryLocations) { - ctx.set(DELIVERY_LOCATIONS, locations) + suspend fun notifyDeliveryLocations(locations: DeliveryLocations) { + state().set(DELIVERY_LOCATIONS, locations) } /** @@ -43,8 +43,8 @@ class OrderETAService { * has moved to a new location. */ @Handler - suspend fun notifyDeliveryPickup(ctx: ObjectContext) { - ctx.set(DELIVERY_IS_PICKED_UP, Unit) + suspend fun notifyDeliveryPickup() { + state().set(DELIVERY_IS_PICKED_UP, Unit) } /** @@ -53,14 +53,16 @@ class OrderETAService { * has moved to a new location. */ @Handler - suspend fun notifyDriverLocationUpdate(ctx: ObjectContext, newLocation: Location) { + suspend fun notifyDriverLocationUpdate(newLocation: Location) { + val state = state() // Retrieve the delivery information for this delivery val locations = - ctx.get(DELIVERY_LOCATIONS) + state.get(DELIVERY_LOCATIONS) ?: throw TerminalException( - "Driver is doing a delivery but there is no ongoing delivery.") + "Driver is doing a delivery but there is no ongoing delivery." + ) - val isOrderPickedUp = ctx.get(DELIVERY_IS_PICKED_UP) != null + val isOrderPickedUp = state.get(DELIVERY_IS_PICKED_UP) != null // Parse the new location, and calculate the ETA of the delivery to the customer val eta = @@ -70,6 +72,6 @@ class OrderETAService { GeoUtils.calculateEtaMillis(locations.restaurant, locations.customer)) // Update the ETA of the order - ctx.set(ORDER_ETA, eta) + state.set(ORDER_ETA, eta) } } diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/OrderWorkflow.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/OrderWorkflow.kt index cbd78843..d6521c63 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/OrderWorkflow.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/OrderWorkflow.kt @@ -13,11 +13,11 @@ package dev.restate.sdk.examples import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.Shared import dev.restate.sdk.annotation.VirtualObject +import dev.restate.sdk.common.TerminalException import dev.restate.sdk.examples.clients.PaymentClient import dev.restate.sdk.examples.clients.RestaurantClient import dev.restate.sdk.examples.utils.GeoUtils import dev.restate.sdk.kotlin.* -import dev.restate.sdk.common.TerminalException import kotlin.time.Duration.Companion.milliseconds /** @@ -35,53 +35,54 @@ class OrderWorkflow { } @Handler - suspend fun process(ctx: ObjectContext, order: OrderRequest) { + suspend fun process(order: OrderRequest) { + val state = state() // --- 📝 Created - ctx.set(STATUS, Status.CREATED) + state.set(STATUS, Status.CREATED) // --- 💳 Payment - val token: String = ctx.random().nextUUID().toString() - val paid: Boolean = ctx.runBlock { PaymentClient.charge(order.orderId, token, order.totalCost) } + val token: String = random().nextUUID().toString() + val paid: Boolean = runBlock { PaymentClient.charge(order.orderId, token, order.totalCost) } if (!paid) { - ctx.set(STATUS, Status.REJECTED) + state.set(STATUS, Status.REJECTED) return } // --- 🕧 Scheduled - ctx.set(STATUS, Status.SCHEDULED) - ctx.sleep(order.deliveryDelay.milliseconds) + state.set(STATUS, Status.SCHEDULED) + sleep(order.deliveryDelay.milliseconds) // --- 🧑‍🍳 Preparing - val preparationAwakeable = ctx.awakeable() - ctx.runBlock { RestaurantClient.prepare(order.orderId, preparationAwakeable.id) } - ctx.set(STATUS, Status.IN_PREPARATION) + val preparationAwakeable = awakeable() + runBlock { RestaurantClient.prepare(order.orderId, preparationAwakeable.id) } + state.set(STATUS, Status.IN_PREPARATION) preparationAwakeable.await() // Declare pick-up and delivery signals - val pickupAwakeable = ctx.awakeable() - ctx.set(PICKUP_CALLBACK_ID, pickupAwakeable.id) - val deliveryAwakeable = ctx.awakeable() - ctx.set(DELIVERY_CALLBACK_ID, deliveryAwakeable.id) + val pickupAwakeable = awakeable() + state.set(PICKUP_CALLBACK_ID, pickupAwakeable.id) + val deliveryAwakeable = awakeable() + state.set(DELIVERY_CALLBACK_ID, deliveryAwakeable.id) // 🎙️ Scheduling delivery - ctx.set(STATUS, Status.SCHEDULING_DELIVERY) - startDelivery(ctx, order) + state.set(STATUS, Status.SCHEDULING_DELIVERY) + startDelivery(order) // 🔜 Waiting for a driver - ctx.set(STATUS, Status.WAITING_FOR_DRIVER) + state.set(STATUS, Status.WAITING_FOR_DRIVER) // 🚴 Delivery pickupAwakeable.await() - ctx.set(STATUS, Status.IN_DELIVERY) + state.set(STATUS, Status.IN_DELIVERY) // 😋 Delivered deliveryAwakeable.await() - ctx.set(STATUS, Status.DELIVERED) + state.set(STATUS, Status.DELIVERED) } @Shared - suspend fun getStatus(ctx: SharedObjectContext): Status { - return ctx.get(STATUS) ?: Status.NEW + suspend fun getStatus(): Status { + return state().get(STATUS) ?: Status.NEW } /** @@ -89,14 +90,14 @@ class OrderWorkflow { * when the driver has arrived at the restaurant. */ @Shared - suspend fun notifyDeliveryPickup(ctx: SharedObjectContext) { + suspend fun notifyDeliveryPickup() { // Retrieve the delivery information for this delivery val callbackId = - ctx.get(PICKUP_CALLBACK_ID) + state().get(PICKUP_CALLBACK_ID) ?: throw TerminalException("Delivery was picked up but there is no ongoing delivery.") // Notify that the delivery was picked up - ctx.awakeableHandle(callbackId).resolve(Unit) + awakeableHandle(callbackId).resolve(Unit) } /** @@ -104,37 +105,41 @@ class OrderWorkflow { * when the driver has delivered the order to the customer. */ @Shared - suspend fun notifyDeliveryDelivered(ctx: SharedObjectContext) { + suspend fun notifyDeliveryDelivered() { // Retrieve the delivery information for this delivery val callbackId = - ctx.get(DELIVERY_CALLBACK_ID) + state().get(DELIVERY_CALLBACK_ID) ?: throw TerminalException("Delivery was delivered but there is no ongoing delivery.") // Notify that the delivery has been completed - ctx.awakeableHandle(callbackId).resolve(Unit) + awakeableHandle(callbackId).resolve(Unit) } /* Coordinates DriverDeliveryMatcher and DriverDigitalTwin to start a delivery */ - private suspend fun startDelivery(ctx: ObjectContext, order: OrderRequest) { + private suspend fun startDelivery(order: OrderRequest) { // --- Wait for a driver to be available and acquire it - val driverAwakeable = ctx.awakeable() - DriverDeliveryMatcherClient.fromContext(ctx, GeoUtils.DEMO_REGION) + val driverAwakeable = awakeable() + toVirtualObject(GeoUtils.DEMO_REGION) + .request { requestDriverForDelivery(driverAwakeable.id) } .send() - .requestDriverForDelivery(driverAwakeable.id) // Wait until the driver pool service has located a driver // This awakeable gets resolved either immediately when there is a pending delivery // or later, when a new delivery comes in. val driverId = driverAwakeable.await() // --- Generate random restaurant/customer locations - val restaurantLocation = ctx.runBlock { GeoUtils.randomLocation() } - val customerLocation = ctx.runBlock { GeoUtils.randomLocation() } + val restaurantLocation = runBlock { GeoUtils.randomLocation() } + val customerLocation = runBlock { GeoUtils.randomLocation() } // --- Assign the driver to the job - DriverDigitalTwinClient.fromContext(ctx, driverId) + virtualObject(driverId) .assignDeliveryJob( AssignDeliveryRequest( - order.orderId, order.restaurantId, restaurantLocation, customerLocation)) - .await() + order.orderId, + order.restaurantId, + restaurantLocation, + customerLocation, + ) + ) } } diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/clients/KafkaPublisher.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/clients/KafkaPublisher.kt index 483c5692..bc3cbb45 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/clients/KafkaPublisher.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/clients/KafkaPublisher.kt @@ -30,9 +30,13 @@ object KafkaPublisher { val properties = Properties() properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_BOOTSTRAP_SERVERS) properties.setProperty( - ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer::class.java.name) + ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + StringSerializer::class.java.name, + ) properties.setProperty( - ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer::class.java.name) + ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + ByteArraySerializer::class.java.name, + ) return properties } } diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/clients/RestaurantClient.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/clients/RestaurantClient.kt index 5e2cb168..89a7e790 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/clients/RestaurantClient.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/clients/RestaurantClient.kt @@ -37,7 +37,8 @@ object RestaurantClient { val response: HttpResponse<*> = httpClient.send(request, HttpResponse.BodyHandlers.discarding()) if (response.statusCode() != 200) { throw TerminalException( - "Prepare request to restaurant failed with status code: " + response.statusCode()) + "Prepare request to restaurant failed with status code: " + response.statusCode() + ) } } } diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/external/DriverMobileAppSimulator.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/external/DriverMobileAppSimulator.kt index 0d074d32..968c2988 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/external/DriverMobileAppSimulator.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/external/DriverMobileAppSimulator.kt @@ -12,13 +12,13 @@ package dev.restate.sdk.examples.external import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.VirtualObject +import dev.restate.sdk.common.TerminalException import dev.restate.sdk.examples.AssignedDelivery -import dev.restate.sdk.examples.DriverDigitalTwinClient +import dev.restate.sdk.examples.DriverDigitalTwin import dev.restate.sdk.examples.Location import dev.restate.sdk.examples.clients.KafkaPublisher import dev.restate.sdk.examples.utils.GeoUtils import dev.restate.sdk.kotlin.* -import dev.restate.sdk.common.TerminalException import kotlin.time.Duration.Companion.milliseconds import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -48,24 +48,24 @@ class DriverMobileAppSimulator { /** Mimics the driver setting himself to available in the app */ @Handler - suspend fun startDriver(ctx: ObjectContext) { + suspend fun startDriver() { + val state = state() // If this driver was already created, do nothing - if (ctx.get(CURRENT_LOCATION) != null) { + if (state.get(CURRENT_LOCATION) != null) { return } - logger.info("Starting driver " + ctx.key()) - val location = ctx.runBlock { GeoUtils.randomLocation() } - ctx.set(CURRENT_LOCATION, location) - KafkaPublisher.sendDriverUpdate(ctx.key(), Json.encodeToString(location).encodeToByteArray()) + val driverId = objectKey() + logger.info("Starting driver " + driverId) + val location = runBlock { GeoUtils.randomLocation() } + state.set(CURRENT_LOCATION, location) + KafkaPublisher.sendDriverUpdate(driverId, Json.encodeToString(location).encodeToByteArray()) // Tell the digital twin of the driver in the food ordering app, that he is available - DriverDigitalTwinClient.fromContext(ctx, ctx.key()) - .setDriverAvailable(GeoUtils.DEMO_REGION) - .await() + virtualObject(driverId).setDriverAvailable(GeoUtils.DEMO_REGION) // Start polling for work - DriverMobileAppSimulatorClient.fromContext(ctx, ctx.key()).send().pollForWork() + toVirtualObject(driverId).request { pollForWork() }.send() } /** @@ -73,16 +73,17 @@ class DriverMobileAppSimulator { * again after a short delay. */ @Handler - suspend fun pollForWork(ctx: ObjectContext) { - val thisDriverSim = DriverMobileAppSimulatorClient.fromContext(ctx, ctx.key()) + suspend fun pollForWork() { + val driverId = objectKey() // Ask the digital twin of the driver in the food ordering app, if he already got a job assigned - val getAssignedDeliveryResult = - DriverDigitalTwinClient.fromContext(ctx, ctx.key()).getAssignedDelivery().await() + val getAssignedDeliveryResult = virtualObject(driverId).getAssignedDelivery() // If there is no job, ask again after a short delay if (getAssignedDeliveryResult.assignedDelivery == null) { - thisDriverSim.send().pollForWork(POLL_INTERVAL.milliseconds) + toVirtualObject(driverId) + .request { pollForWork() } + .send(POLL_INTERVAL.milliseconds) return } @@ -93,21 +94,25 @@ class DriverMobileAppSimulator { getAssignedDeliveryResult.assignedDelivery.orderId, getAssignedDeliveryResult.assignedDelivery.restaurantId, getAssignedDeliveryResult.assignedDelivery.restaurantLocation, - getAssignedDeliveryResult.assignedDelivery.customerLocation) - ctx.set(ASSIGNED_DELIVERY, newAssignedDelivery) + getAssignedDeliveryResult.assignedDelivery.customerLocation, + ) + state().set(ASSIGNED_DELIVERY, newAssignedDelivery) // Start moving to the delivery pickup location - thisDriverSim.send().move(MOVE_INTERVAL.milliseconds) + toVirtualObject(driverId) + .request { move() } + .send(MOVE_INTERVAL.milliseconds) } /** Periodically lets the food ordering app know the new location */ @Handler - suspend fun move(ctx: ObjectContext) { - val thisDriverSim = DriverMobileAppSimulatorClient.fromContext(ctx, ctx.key()) + suspend fun move() { + val driverId = objectKey() + val state = state() val assignedDelivery = - ctx.get(ASSIGNED_DELIVERY) ?: throw TerminalException("Driver has no delivery assigned") + state.get(ASSIGNED_DELIVERY) ?: throw TerminalException("Driver has no delivery assigned") val currentLocation = - ctx.get(CURRENT_LOCATION) ?: throw TerminalException("Driver has no location assigned") + state.get(CURRENT_LOCATION) ?: throw TerminalException("Driver has no location assigned") // Get next destination to go to val nextDestination = @@ -116,29 +121,29 @@ class DriverMobileAppSimulator { // Move to the next location val newLocation: Location = GeoUtils.moveToDestination(currentLocation, nextDestination) - ctx.set(CURRENT_LOCATION, newLocation) - KafkaPublisher.sendDriverUpdate(ctx.key(), Json.encodeToString(newLocation).encodeToByteArray()) + state.set(CURRENT_LOCATION, newLocation) + KafkaPublisher.sendDriverUpdate(driverId, Json.encodeToString(newLocation).encodeToByteArray()) // If we reached the destination, notify the food ordering app if (newLocation.equals(nextDestination)) { // If the delivery was already picked up, then that means it now arrived at the customer if (assignedDelivery.isOrderPickedUp) { // Delivery is delivered to customer - ctx.clear(ASSIGNED_DELIVERY) + state.clear(ASSIGNED_DELIVERY) // Notify the driver's digital twin in the food ordering app of the delivery success - DriverDigitalTwinClient.fromContext(ctx, ctx.key()).notifyDeliveryDelivered().await() + virtualObject(driverId).notifyDeliveryDelivered() // Take a small break before starting the next delivery - ctx.sleep(PAUSE_BETWEEN_DELIVERIES.milliseconds) + sleep(PAUSE_BETWEEN_DELIVERIES.milliseconds) // Tell the driver's digital twin in the food ordering app, that he is available - DriverDigitalTwinClient.fromContext(ctx, ctx.key()) + toVirtualObject(driverId) + .request { setDriverAvailable(GeoUtils.DEMO_REGION) } .send() - .setDriverAvailable(GeoUtils.DEMO_REGION) // Start polling for work - DriverMobileAppSimulatorClient.fromContext(ctx, ctx.key()).send().pollForWork() + toVirtualObject(driverId).request { pollForWork() }.send() return } @@ -146,11 +151,13 @@ class DriverMobileAppSimulator { // restaurant // and will start the delivery assignedDelivery.notifyPickup() - ctx.set(ASSIGNED_DELIVERY, assignedDelivery) - DriverDigitalTwinClient.fromContext(ctx, ctx.key()).notifyDeliveryPickup().await() + state.set(ASSIGNED_DELIVERY, assignedDelivery) + virtualObject(driverId).notifyDeliveryPickup() } // Call this method again after a short delay - thisDriverSim.send().move(MOVE_INTERVAL.milliseconds) + toVirtualObject(driverId) + .request { move() } + .send(MOVE_INTERVAL.milliseconds) } } diff --git a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/types.kt b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/types.kt index 2c597933..7849cb97 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/types.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restate-app/src/main/kotlin/dev/restate/sdk/examples/types.kt @@ -17,7 +17,7 @@ data class AssignDeliveryRequest( val orderId: String, val restaurantId: String, val restaurantLocation: Location, - val customerLocation: Location + val customerLocation: Location, ) @Serializable @@ -27,7 +27,7 @@ data class AssignedDelivery( val restaurantId: String, val restaurantLocation: Location, val customerLocation: Location, - var isOrderPickedUp: Boolean = false + var isOrderPickedUp: Boolean = false, ) { fun notifyPickup() { isOrderPickedUp = true @@ -41,7 +41,7 @@ data class DeliveryInformation( val restaurantId: String, val restaurantLocation: Location, val customerLocation: Location, - var isOrderPickedUp: Boolean + var isOrderPickedUp: Boolean, ) { fun notifyPickup() { this.isOrderPickedUp = true @@ -60,7 +60,7 @@ data class DeliveryLocations( enum class DriverStatus { IDLE, WAITING_FOR_WORK, - DELIVERING + DELIVERING, } @Serializable data class Location(val lon: Double, val lat: Double) @@ -71,7 +71,7 @@ data class OrderRequest( val restaurantId: String, val products: List, val totalCost: Double, - val deliveryDelay: Int + val deliveryDelay: Int, ) @Serializable data class Product(val productId: String, val description: String, val quantity: Int) @@ -88,7 +88,7 @@ enum class Status { DELIVERED, REJECTED, CANCELLED, - UNKNOWN + UNKNOWN, } @Serializable data class GetAssignedDeliveryResult(val assignedDelivery: AssignedDelivery? = null) diff --git a/kotlin/end-to-end-applications/food-ordering/app/restaurant/build.gradle.kts b/kotlin/end-to-end-applications/food-ordering/app/restaurant/build.gradle.kts index 8ac0d9a2..8021bef2 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restaurant/build.gradle.kts +++ b/kotlin/end-to-end-applications/food-ordering/app/restaurant/build.gradle.kts @@ -1,13 +1,13 @@ plugins { application - id("com.google.cloud.tools.jib") version "3.4.0" + id("com.google.cloud.tools.jib") version "3.5.3" } repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { // Kafka @@ -20,7 +20,13 @@ dependencies { } // Set main class -application { mainClass.set("dev.restate.sdk.examples.RestaurantMainKt") } +application { + mainClass.set("dev.restate.sdk.examples.RestaurantMainKt") + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, + // --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = + listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} jib { to.image = "restaurant-app:0.0.1" diff --git a/kotlin/end-to-end-applications/food-ordering/app/restaurant/src/main/kotlin/dev/restate/sdk/examples/RestaurantMain.kt b/kotlin/end-to-end-applications/food-ordering/app/restaurant/src/main/kotlin/dev/restate/sdk/examples/RestaurantMain.kt index 1598741f..b74fbc8e 100644 --- a/kotlin/end-to-end-applications/food-ordering/app/restaurant/src/main/kotlin/dev/restate/sdk/examples/RestaurantMain.kt +++ b/kotlin/end-to-end-applications/food-ordering/app/restaurant/src/main/kotlin/dev/restate/sdk/examples/RestaurantMain.kt @@ -3,15 +3,15 @@ package dev.restate.sdk.examples import com.sun.net.httpserver.HttpExchange import com.sun.net.httpserver.HttpHandler import com.sun.net.httpserver.HttpServer -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.Logger import dev.restate.client.Client import dev.restate.serde.kotlinx.* +import java.net.InetSocketAddress import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream -import java.net.InetSocketAddress +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger /** * Restaurant POS system (HTTP server) that receives preparation requests for orders from the @@ -35,7 +35,10 @@ fun main() { /** Preparation request handler. */ internal class PrepareHandler : HttpHandler { - private val ingressClient: Client = Client.connect(RESTATE_RUNTIME_ENDPOINT) + // Kotlin ingress calls (de)serialize with kotlinx.serialization, so build the client + // with the KotlinSerializationSerdeFactory. + private val ingressClient: Client = + Client.connect(RESTATE_RUNTIME_ENDPOINT, KotlinSerializationSerdeFactory()) @OptIn(ExperimentalSerializationApi::class) override fun handle(t: HttpExchange) { diff --git a/kotlin/end-to-end-applications/kmp-android-todo-app/gradle/libs.versions.toml b/kotlin/end-to-end-applications/kmp-android-todo-app/gradle/libs.versions.toml index f3445a73..43945cd3 100644 --- a/kotlin/end-to-end-applications/kmp-android-todo-app/gradle/libs.versions.toml +++ b/kotlin/end-to-end-applications/kmp-android-todo-app/gradle/libs.versions.toml @@ -11,9 +11,12 @@ androidx-espresso-core = "3.6.1" androidx-lifecycle = "2.8.0" androidx-material = "1.12.0" androidx-test-junit = "1.2.1" +# NOTE: bumped `kotlin` to 2.4.0 for the Restate server module (Java 25 needs Kotlin >= 2.2.20). +# The Compose Multiplatform plugin below likely needs a matching bump for the Android/composeApp +# modules to build on an Android-SDK machine — verify the Android app separately. compose-plugin = "1.6.11" junit = "4.13.2" -kotlin = "2.2.10" +kotlin = "2.4.0" logback = "1.5.7" kotlinx-serialization = "1.7.2" ktor = "2.3.12" diff --git a/kotlin/end-to-end-applications/kmp-android-todo-app/server/build.gradle.kts b/kotlin/end-to-end-applications/kmp-android-todo-app/server/build.gradle.kts index d3b9bb7d..7f5a0172 100644 --- a/kotlin/end-to-end-applications/kmp-android-todo-app/server/build.gradle.kts +++ b/kotlin/end-to-end-applications/kmp-android-todo-app/server/build.gradle.kts @@ -2,24 +2,29 @@ plugins { alias(libs.plugins.kotlinJvm) application - id("com.google.devtools.ksp") version "2.2.10-2.0.2" + // Restate proxy clients need non-final classes; the all-open plugin opens + // classes annotated with the Restate annotations (see allOpen block below). + id("org.jetbrains.kotlin.plugin.allopen") version "2.4.0" } group = "dev.restate.examples.noteapp" version = "1.0.0" -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" kotlin { - jvmToolchain(17) + jvmToolchain(25) +} + +allOpen { + annotation("dev.restate.sdk.annotation.Service") + annotation("dev.restate.sdk.annotation.VirtualObject") + annotation("dev.restate.sdk.annotation.Workflow") } dependencies { implementation(projects.shared) - // Annotation processor - ksp("dev.restate:sdk-api-kotlin-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-kotlin-http:$restateVersion") @@ -28,4 +33,6 @@ dependencies { application { mainClass.set("dev.restate.examples.noteapp.ApplicationKt") + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } \ No newline at end of file diff --git a/kotlin/end-to-end-applications/kmp-android-todo-app/server/src/main/kotlin/dev/restate/examples/noteapp/Application.kt b/kotlin/end-to-end-applications/kmp-android-todo-app/server/src/main/kotlin/dev/restate/examples/noteapp/Application.kt index dbb42bef..761e4d8d 100644 --- a/kotlin/end-to-end-applications/kmp-android-todo-app/server/src/main/kotlin/dev/restate/examples/noteapp/Application.kt +++ b/kotlin/end-to-end-applications/kmp-android-todo-app/server/src/main/kotlin/dev/restate/examples/noteapp/Application.kt @@ -17,12 +17,12 @@ class Todos { } @Handler - suspend fun add(ctx: ObjectContext, item: TodoItem) = ctx.update(TODOS) { + suspend fun add(item: TodoItem) = update(TODOS) { (it ?: emptyMap()) + (item.id to item) } @Handler - suspend fun markCompleted(ctx: ObjectContext, id: String) = ctx.update(TODOS) { + suspend fun markCompleted(id: String) = update(TODOS) { val newNote = (it ?: emptyMap()) .getOrElse(id) { throw TerminalException(404, "Note not found for given index $id") } .copy(isCompleted = true) @@ -31,7 +31,7 @@ class Todos { } @Handler - suspend fun remove(ctx: ObjectContext, id: String) = ctx.update(TODOS) { + suspend fun remove(id: String) = update(TODOS) { val map = it?.toMutableMap() ?: mutableMapOf() if (map.remove(id) == null) { throw TerminalException(404, "Note not found for given index $id") @@ -40,16 +40,18 @@ class Todos { } @Shared - suspend fun readAll(ctx: SharedObjectContext): Collection = - ctx.get(TODOS)?.values ?: emptyList() + suspend fun readAll(): Collection = + state().get(TODOS)?.values ?: emptyList() } // Helper function to update state values -private suspend fun ObjectContext.update(stateKey: StateKey, fn: (T?) -> T) = - this.set(stateKey, fn(this.get(stateKey))) +private suspend fun update(stateKey: StateKey, fn: (T?) -> T) { + val state = state() + state.set(stateKey, fn(state.get(stateKey))) +} fun main() { RestateHttpServer.listen(endpoint { bind(Todos()) }) -} \ No newline at end of file +} diff --git a/kotlin/end-to-end-applications/kmp-android-todo-app/settings.gradle.kts b/kotlin/end-to-end-applications/kmp-android-todo-app/settings.gradle.kts index 85c5c1e7..9b6a0201 100644 --- a/kotlin/end-to-end-applications/kmp-android-todo-app/settings.gradle.kts +++ b/kotlin/end-to-end-applications/kmp-android-todo-app/settings.gradle.kts @@ -15,6 +15,10 @@ pluginManagement { } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + dependencyResolutionManagement { repositories { google { diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/README.md b/kotlin/integrations/kotlin-gradle-lambda-cdk/README.md index d2da6ca5..ed0223b9 100644 --- a/kotlin/integrations/kotlin-gradle-lambda-cdk/README.md +++ b/kotlin/integrations/kotlin-gradle-lambda-cdk/README.md @@ -34,7 +34,7 @@ For more information on CDK, please see [Getting started with the AWS CDK](https * npm * gradle -* JDK >= 21 +* JDK >= 25 * AWS account, bootstrapped for CDK use * valid AWS credentials with sufficient privileges to create the necessary resources * an existing [Restate Cloud](https://restate.dev) environment (environment id + API key) diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/cdk/lambda-jvm-cdk-stack.ts b/kotlin/integrations/kotlin-gradle-lambda-cdk/cdk/lambda-jvm-cdk-stack.ts index b621f9ae..203093d5 100644 --- a/kotlin/integrations/kotlin-gradle-lambda-cdk/cdk/lambda-jvm-cdk-stack.ts +++ b/kotlin/integrations/kotlin-gradle-lambda-cdk/cdk/lambda-jvm-cdk-stack.ts @@ -20,7 +20,7 @@ export class LambdaJvmCdkStack extends cdk.Stack { super(scope, id, props); const handler: lambda.Function = new lambda.Function(this, "GreeterService", { - runtime: lambda.Runtime.JAVA_21, + runtime: lambda.Runtime.JAVA_25, architecture: lambda.Architecture.ARM_64, code: lambda.Code.fromAsset("lambda/build/distributions/lambda.zip"), handler: "dev.restate.sdk.examples.LambdaHandler", @@ -28,6 +28,10 @@ export class LambdaJvmCdkStack extends cdk.Stack { loggingFormat: lambda.LoggingFormat.JSON, applicationLogLevelV2: lambda.ApplicationLogLevel.DEBUG, systemLogLevelV2: lambda.SystemLogLevel.INFO, + environment: { + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + JAVA_TOOL_OPTIONS: "--enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow", + }, }); // If you would prefer to manually register the Lambda service with your Restate environment, diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/build.gradle.kts b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/build.gradle.kts index f39198bf..c5f3bf9c 100644 --- a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/build.gradle.kts +++ b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/build.gradle.kts @@ -1,7 +1,7 @@ plugins { - kotlin("jvm") version "2.2.10" - kotlin("plugin.serialization") version "2.2.10" - id("com.google.devtools.ksp") version "2.2.10-2.0.2" + kotlin("jvm") version "2.4.0" + kotlin("plugin.serialization") version "2.4.0" + kotlin("plugin.allopen") version "2.4.0" id("distribution") } @@ -10,12 +10,16 @@ repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" -dependencies { - // Annotation processor - ksp("dev.restate:sdk-api-kotlin-gen:$restateVersion") +// Restate proxy clients require non-final annotated classes +allOpen { + annotation("dev.restate.sdk.annotation.Service") + annotation("dev.restate.sdk.annotation.VirtualObject") + annotation("dev.restate.sdk.annotation.Workflow") +} +dependencies { // Restate SDK implementation("dev.restate:sdk-kotlin-lambda:$restateVersion") @@ -27,10 +31,8 @@ dependencies { } // Setup Java/Kotlin compiler target -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } +kotlin { + jvmToolchain(25) } tasks.register("lambdaZip") { @@ -44,4 +46,4 @@ tasks.register("lambdaZip") { tasks.build { dependsOn("lambdaZip") -} \ No newline at end of file +} diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.jar b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.jar index 033e24c4..b1b8ef56 100644 Binary files a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.jar and b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.properties b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.properties index 37f853b1..a9db1155 100644 --- a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradlew b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradlew index fcb6fca1..249efbb0 100755 --- a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradlew +++ b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -15,10 +15,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -27,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -55,7 +57,7 @@ # 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 +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -111,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -144,7 +146,7 @@ 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=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -169,7 +171,6 @@ fi # 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" ) @@ -201,16 +202,15 @@ 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, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_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 \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradlew.bat b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradlew.bat index 6689b85b..8508ef68 100644 --- a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradlew.bat +++ b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/gradlew.bat @@ -13,16 +13,18 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -43,13 +45,13 @@ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -57,36 +59,24 @@ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/settings.gradle.kts b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/settings.gradle.kts index 824de76d..e4dbcee6 100644 --- a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/settings.gradle.kts +++ b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/settings.gradle.kts @@ -1,2 +1,2 @@ // automatically download missing toolchains -plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0" } \ No newline at end of file +plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } \ No newline at end of file diff --git a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/src/main/kotlin/dev/restate/sdk/examples/Greeter.kt b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/src/main/kotlin/dev/restate/sdk/examples/Greeter.kt index d6414461..9e0939ca 100644 --- a/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/src/main/kotlin/dev/restate/sdk/examples/Greeter.kt +++ b/kotlin/integrations/kotlin-gradle-lambda-cdk/lambda/src/main/kotlin/dev/restate/sdk/examples/Greeter.kt @@ -13,7 +13,7 @@ package dev.restate.sdk.examples import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.Service -import dev.restate.sdk.kotlin.Context +import dev.restate.sdk.kotlin.* /** * Template of a Restate service and handler @@ -23,7 +23,7 @@ import dev.restate.sdk.kotlin.Context class Greeter { @Handler - suspend fun greet(ctx: Context, name: String): String { + suspend fun greet(name: String): String { return "Hello, $name!" } } diff --git a/kotlin/patterns-use-cases/build.gradle.kts b/kotlin/patterns-use-cases/build.gradle.kts index d951fe8b..b120d1b8 100644 --- a/kotlin/patterns-use-cases/build.gradle.kts +++ b/kotlin/patterns-use-cases/build.gradle.kts @@ -1,19 +1,18 @@ plugins { application - kotlin("jvm") version "2.2.10" - kotlin("plugin.serialization") version "2.2.10" - id("com.google.devtools.ksp") version "2.2.10-2.0.2" - id("com.diffplug.spotless") version "6.25.0" + kotlin("jvm") version "2.4.0" + kotlin("plugin.serialization") version "2.4.0" + kotlin("plugin.allopen") version "2.4.0" + id("com.diffplug.spotless") version "8.8.0" } -repositories { mavenCentral() } +repositories { + mavenCentral() +} -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - // Annotation processor - ksp("dev.restate:sdk-api-kotlin-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-kotlin-http:$restateVersion") @@ -24,7 +23,14 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") } -kotlin { jvmToolchain(21) } +kotlin { jvmToolchain(25) } + +allOpen { + // Restate proxy clients need non-final classes. + annotation("dev.restate.sdk.annotation.Service") + annotation("dev.restate.sdk.annotation.VirtualObject") + annotation("dev.restate.sdk.annotation.Workflow") +} // Configure main class application { @@ -33,6 +39,10 @@ application { } else { mainClass.set("my.example.sagas.BookingWorkflowKt") } + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, + // --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = + listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } spotless { diff --git a/kotlin/patterns-use-cases/gradle/wrapper/gradle-wrapper.jar b/kotlin/patterns-use-cases/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/kotlin/patterns-use-cases/gradle/wrapper/gradle-wrapper.jar and b/kotlin/patterns-use-cases/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/patterns-use-cases/gradle/wrapper/gradle-wrapper.properties b/kotlin/patterns-use-cases/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/kotlin/patterns-use-cases/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/patterns-use-cases/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin/patterns-use-cases/gradlew b/kotlin/patterns-use-cases/gradlew index faf93008..249efbb0 100755 --- a/kotlin/patterns-use-cases/gradlew +++ b/kotlin/patterns-use-cases/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/kotlin/patterns-use-cases/gradlew.bat b/kotlin/patterns-use-cases/gradlew.bat index 9b42019c..8508ef68 100644 --- a/kotlin/patterns-use-cases/gradlew.bat +++ b/kotlin/patterns-use-cases/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventenrichment/PackageTracker.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventenrichment/PackageTracker.kt index 6a7aded1..3901bd52 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventenrichment/PackageTracker.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventenrichment/PackageTracker.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.Serializable @Serializable data class PackageInfo( val finalDestination: String, - val locations: MutableList = mutableListOf() + val locations: MutableList = mutableListOf(), ) @Serializable data class LocationUpdate(val timestamp: String, val location: String) @@ -25,21 +25,21 @@ class PackageTracker { } @Handler - suspend fun registerPackage(ctx: ObjectContext, packageInfo: PackageInfo) { - ctx.set(PACKAGE_INFO, packageInfo) + suspend fun registerPackage(packageInfo: PackageInfo) { + state().set(PACKAGE_INFO, packageInfo) } @Handler - suspend fun updateLocation(ctx: ObjectContext, locationUpdate: LocationUpdate) { - val packageInfo = ctx.get(PACKAGE_INFO) ?: throw TerminalException("Package not found") + suspend fun updateLocation(locationUpdate: LocationUpdate) { + val packageInfo = state().get(PACKAGE_INFO) ?: throw TerminalException("Package not found") packageInfo.locations.add(locationUpdate) - ctx.set(PACKAGE_INFO, packageInfo) + state().set(PACKAGE_INFO, packageInfo) } @Shared - suspend fun getPackageInfo(ctx: SharedObjectContext): PackageInfo { - return ctx.get(PACKAGE_INFO) ?: throw TerminalException("Package not found") + suspend fun getPackageInfo(): PackageInfo { + return state().get(PACKAGE_INFO) ?: throw TerminalException("Package not found") } } diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventtransactions/UserFeed.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventtransactions/UserFeed.kt index 31c5ed9e..8b9c92d9 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventtransactions/UserFeed.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventtransactions/UserFeed.kt @@ -17,16 +17,16 @@ class UserFeed { @Serializable data class SocialMediaPost(val content: String, val metadata: String) @Handler - suspend fun processPost(ctx: ObjectContext, post: SocialMediaPost) { - val userId = ctx.key() + suspend fun processPost(post: SocialMediaPost) { + val userId = objectKey() - val postId = ctx.runBlock { createPost(userId, post) } + val postId = runBlock { createPost(userId, post) } - while (ctx.runBlock { getPostStatus(postId) } == "PENDING") { - ctx.sleep(5000.milliseconds) + while (runBlock { getPostStatus(postId) } == "PENDING") { + sleep(5000.milliseconds) } - ctx.runBlock { updateUserFeed(userId, postId) } + runBlock { updateUserFeed(userId, postId) } } } diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventtransactions/utils/stubs.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventtransactions/utils/stubs.kt index 9e725a73..366d0e3c 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventtransactions/utils/stubs.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/eventtransactions/utils/stubs.kt @@ -16,7 +16,9 @@ fun createPost(userId: String, post: UserFeed.SocialMediaPost): String { fun getPostStatus(postId: String): String { return if (Math.random() < 0.8) { logger.info( - "Content moderation for post {} is still pending... Will check again in 5 seconds", postId) + "Content moderation for post {} is still pending... Will check again in 5 seconds", + postId, + ) "PENDING" } else { logger.info("Content moderation for post {} is done", postId) diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/FanOutWorker.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/FanOutWorker.kt index b0591edd..10c25acd 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/FanOutWorker.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/FanOutWorker.kt @@ -29,22 +29,25 @@ import dev.restate.sdk.kotlin.endpoint.endpoint @Service class FanOutWorker { @Handler - suspend fun run(ctx: Context, task: Task): TaskResult { + suspend fun run(task: Task): TaskResult { // Split the task in subtasks - val subTasks = ctx.runBlock { task.split() } + val subTasks = runBlock { task.split() } // Fan out the subtasks - run them in parallel // Fan in - Await all results and aggregate - val results = subTasks.map { FanOutWorkerClient.fromContext(ctx).runSubtask(it) }.awaitAll() + val results = + subTasks + .map { subTask -> toService().request { runSubtask(subTask) }.call() } + .awaitAll() return results.aggregate() } @Handler - suspend fun runSubtask(ctx: Context, subTask: SubTask): SubTaskResult { + suspend fun runSubtask(subTask: SubTask): SubTaskResult { // Processing logic goes here ... // Can be moved to a separate service to scale independently - return subTask.execute(ctx) + return subTask.execute() } } diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/stubs.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/stubs.kt index 57fa012f..7eb491df 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/stubs.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/stubs.kt @@ -1,6 +1,6 @@ package my.example.parallelizework -import dev.restate.sdk.kotlin.Context +import dev.restate.sdk.kotlin.* import kotlin.time.Duration.Companion.seconds import kotlinx.serialization.Serializable import org.apache.logging.log4j.LogManager @@ -18,11 +18,11 @@ private val logger = LogManager.getLogger("FanOutWorker") // Split the task into subTasks fun Task.split() = this.description.split(",").map { SubTask(it) } -suspend fun SubTask.execute(ctx: Context): SubTaskResult { +suspend fun SubTask.execute(): SubTaskResult { // Execute subtask logger.info("Started executing subtask: {}", this.description) // Sleep for a random amount between 0 and 10 seconds - ctx.sleep(ctx.random().nextInt(0, 10).toLong().seconds) + sleep(random().nextInt(0, 10).toLong().seconds) logger.info("Execution subtask finished: {}", this.description) return SubTaskResult("${this.description}: DONE") } diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/queue/AsyncTaskService.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/queue/AsyncTaskService.kt index 60429a06..2047305d 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/queue/AsyncTaskService.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/queue/AsyncTaskService.kt @@ -3,14 +3,13 @@ package my.example.queue import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.Service import dev.restate.sdk.http.vertx.RestateHttpServer -import dev.restate.sdk.kotlin.* import dev.restate.sdk.kotlin.endpoint.endpoint import kotlinx.serialization.Serializable @Service class AsyncTaskService { @Handler - suspend fun runTask(ctx: Context, params: TaskOpts): String { + suspend fun runTask(params: TaskOpts): String { return params.someHeavyWork() } } diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/queue/TaskSubmitter.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/queue/TaskSubmitter.kt index 239cbda8..de091403 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/queue/TaskSubmitter.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/queue/TaskSubmitter.kt @@ -2,6 +2,7 @@ package my.example.queue import dev.restate.client.Client import dev.restate.client.kotlin.* +import dev.restate.serde.kotlinx.KotlinSerializationSerdeFactory /* * Restate is as a sophisticated task queue, with extra features like: @@ -15,21 +16,25 @@ import dev.restate.client.kotlin.* */ class TaskSubmitter { companion object { - private val restateClient: Client = Client.connect("http://localhost:8080") + // A Kotlin service()/virtualObject()/workflow() proxy serializes with + // kotlinx.serialization, so the client must be built with the kotlinx serde factory. + private val restateClient: Client = + Client.connect("http://localhost:8080", KotlinSerializationSerdeFactory()) } suspend fun TaskOpts.scheduleTask() { // submit the task; similar to publishing a message to a queue // Restate ensures the task is executed exactly once val handle = - AsyncTaskServiceClient.fromClient(restateClient).send().runTask( - this, - // optionally add a delay to execute the task later - // delay = 5.days, - ) { - // use a stable uuid as an idempotency key; Restate deduplicates for us - idempotencyKey = "dQw4w9WgXcQ" - } + restateClient + .toService() + .request { runTask(this@scheduleTask) } + // optionally add a delay to execute the task later, e.g. .send(5.days) + .options { + // use a stable uuid as an idempotency key; Restate deduplicates for us + idempotencyKey = "dQw4w9WgXcQ" + } + .send() // ... do other things while the task is being processed ... diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/BookingWorkflow.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/BookingWorkflow.kt index 8e1cb20d..ae1f73d2 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/BookingWorkflow.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/BookingWorkflow.kt @@ -22,7 +22,7 @@ data class BookingRequest( val customerId: String, val flight: FlightBookingRequest, val car: CarRentalBookingRequest, - val hotel: HotelRequest + val hotel: HotelRequest, ) /* @@ -54,21 +54,21 @@ Note: that the compensation logic is purely implemented in user code (no special class BookingWorkflow { @Handler - suspend fun run(ctx: Context, req: BookingRequest) { + suspend fun run(req: BookingRequest) { // Create a list of undo actions val compensations = mutableListOf Unit>() try { // For each action, we register a compensation that will be executed on failures - compensations.add { ctx.runBlock("Cancel flight") { cancelFlight(req.customerId) } } - ctx.runBlock("Book flight") { bookFlight(req.customerId, req.flight) } + compensations.add { runBlock("Cancel flight") { cancelFlight(req.customerId) } } + runBlock("Book flight") { bookFlight(req.customerId, req.flight) } - compensations.add { ctx.runBlock("Cancel car") { cancelCar(req.customerId) } } - ctx.runBlock("Book car") { bookCar(req.customerId, req.car) } + compensations.add { runBlock("Cancel car") { cancelCar(req.customerId) } } + runBlock("Book car") { bookCar(req.customerId, req.car) } - compensations.add { ctx.runBlock("Cancel hotel") { cancelHotel(req.customerId) } } - ctx.runBlock("Book hotel") { bookHotel(req.customerId, req.hotel) } + compensations.add { runBlock("Cancel hotel") { cancelHotel(req.customerId) } } + runBlock("Book hotel") { bookHotel(req.customerId, req.hotel) } } // Terminal exceptions are not retried by Restate. We undo previous actions and fail the // workflow. diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/clients/CarRentalClient.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/clients/CarRentalClient.kt index 62909953..f25b739c 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/clients/CarRentalClient.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/clients/CarRentalClient.kt @@ -3,8 +3,7 @@ package my.example.sagas.clients import kotlinx.serialization.Serializable import org.apache.logging.log4j.LogManager -@Serializable -data class CarRentalBookingRequest(val pickupLocation: String, val rentalDate: String) +@Serializable data class CarRentalBookingRequest(val pickupLocation: String, val rentalDate: String) private val logger = LogManager.getLogger("CarRentals") diff --git a/kotlin/patterns-use-cases/src/main/kotlin/my/example/schedulingtasks/PaymentTracker.kt b/kotlin/patterns-use-cases/src/main/kotlin/my/example/schedulingtasks/PaymentTracker.kt index c1604c8e..a4d8c20f 100644 --- a/kotlin/patterns-use-cases/src/main/kotlin/my/example/schedulingtasks/PaymentTracker.kt +++ b/kotlin/patterns-use-cases/src/main/kotlin/my/example/schedulingtasks/PaymentTracker.kt @@ -17,27 +17,29 @@ class PaymentTracker { // Stripe sends us webhook events for invoice payment attempts @Handler - suspend fun onPaymentSuccess(ctx: ObjectContext, event: StripeEvent) { - ctx.set(PAID, true) + suspend fun onPaymentSuccess(event: StripeEvent) { + state().set(PAID, true) } @Handler - suspend fun onPaymentFailure(ctx: ObjectContext, event: StripeEvent) { + suspend fun onPaymentFailure(event: StripeEvent) { + val state = state() + // Already paid, no need to send reminders - if (ctx.get(PAID) == true) { + if (state.get(PAID) == true) { return } - val remindersCount = ctx.get(REMINDER_COUNT) ?: 0 + val remindersCount = state.get(REMINDER_COUNT) ?: 0 if (remindersCount < 3) { - ctx.set(REMINDER_COUNT, remindersCount + 1) - ctx.runBlock { sendReminderEmail(event) } + state.set(REMINDER_COUNT, remindersCount + 1) + runBlock { sendReminderEmail(event) } // Schedule next reminder via a delayed self call - PaymentTrackerClient.fromContext(ctx, ctx.key()).send().onPaymentFailure(event, 1.days) + toVirtualObject(objectKey()).request { onPaymentFailure(event) }.send(1.days) } else { // After three reminders, escalate to support team - ctx.runBlock { escalateToHuman(event) } + runBlock { escalateToHuman(event) } } } } diff --git a/kotlin/templates/kotlin-gradle-spring-boot/build.gradle.kts b/kotlin/templates/kotlin-gradle-spring-boot/build.gradle.kts index 054b6626..8ead032a 100644 --- a/kotlin/templates/kotlin-gradle-spring-boot/build.gradle.kts +++ b/kotlin/templates/kotlin-gradle-spring-boot/build.gradle.kts @@ -1,8 +1,7 @@ plugins { - kotlin("jvm") version "2.2.10" - kotlin("plugin.spring") version "2.2.10" - kotlin("plugin.serialization") version "2.2.10" - id("com.google.devtools.ksp") version "2.2.10-2.0.2" + kotlin("jvm") version "2.4.0" + kotlin("plugin.spring") version "2.4.0" + kotlin("plugin.serialization") version "2.4.0" id("org.springframework.boot") version "3.5.6" id("io.spring.dependency-management") version "1.1.7" @@ -13,7 +12,7 @@ version = "0.0.1-SNAPSHOT" java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(25) } } @@ -21,13 +20,10 @@ repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - // Annotation processor - ksp("dev.restate:sdk-api-kotlin-gen:$restateVersion") - - // Restate SDK + // Restate SDK (the Spring Boot Kotlin starter also applies the all-open plugin for Restate annotations) implementation("dev.restate:sdk-spring-boot-kotlin-starter:$restateVersion") // Testing @@ -46,4 +42,11 @@ kotlin { tasks.withType { useJUnitPlatform() + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") +} + +tasks.named("bootRun") { + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + jvmArgs("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } diff --git a/kotlin/templates/kotlin-gradle-spring-boot/gradle/wrapper/gradle-wrapper.jar b/kotlin/templates/kotlin-gradle-spring-boot/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/kotlin/templates/kotlin-gradle-spring-boot/gradle/wrapper/gradle-wrapper.jar and b/kotlin/templates/kotlin-gradle-spring-boot/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/templates/kotlin-gradle-spring-boot/gradle/wrapper/gradle-wrapper.properties b/kotlin/templates/kotlin-gradle-spring-boot/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/kotlin/templates/kotlin-gradle-spring-boot/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/templates/kotlin-gradle-spring-boot/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin/templates/kotlin-gradle-spring-boot/gradlew b/kotlin/templates/kotlin-gradle-spring-boot/gradlew index faf93008..249efbb0 100755 --- a/kotlin/templates/kotlin-gradle-spring-boot/gradlew +++ b/kotlin/templates/kotlin-gradle-spring-boot/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/kotlin/templates/kotlin-gradle-spring-boot/gradlew.bat b/kotlin/templates/kotlin-gradle-spring-boot/gradlew.bat index 9d21a218..a51ec4f5 100644 --- a/kotlin/templates/kotlin-gradle-spring-boot/gradlew.bat +++ b/kotlin/templates/kotlin-gradle-spring-boot/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/kotlin/templates/kotlin-gradle-spring-boot/src/main/kotlin/com/example/restatestarter/Greeter.kt b/kotlin/templates/kotlin-gradle-spring-boot/src/main/kotlin/com/example/restatestarter/Greeter.kt index a79c40d0..4e6a4330 100644 --- a/kotlin/templates/kotlin-gradle-spring-boot/src/main/kotlin/com/example/restatestarter/Greeter.kt +++ b/kotlin/templates/kotlin-gradle-spring-boot/src/main/kotlin/com/example/restatestarter/Greeter.kt @@ -2,14 +2,16 @@ package com.example.restatestarter import dev.restate.sdk.kotlin.* import dev.restate.sdk.annotation.Handler -import dev.restate.sdk.springboot.RestateService +import dev.restate.sdk.annotation.Service +import dev.restate.sdk.springboot.RestateComponent import org.springframework.beans.factory.annotation.Value import sendNotification import sendReminder import kotlin.time.Duration.Companion.seconds import kotlinx.serialization.Serializable -@RestateService +@RestateComponent +@Service class Greeter { @Value("\${greetingPrefix}") @@ -21,12 +23,12 @@ class Greeter { data class GreetingResponse(val message: String) @Handler - suspend fun greet(ctx: Context, req: Greeting): GreetingResponse { + suspend fun greet(req: Greeting): GreetingResponse { // Durably execute a set of steps; resilient against failures - val greetingId = ctx.random().nextUUID().toString() - ctx.runBlock("Notification") { sendNotification(greetingId, req.name) } - ctx.sleep(1.seconds) - ctx.runBlock("Reminder") { sendReminder(greetingId, req.name) } + val greetingId = random().nextUUID().toString() + runBlock("Notification") { sendNotification(greetingId, req.name) } + sleep(1.seconds) + runBlock("Reminder") { sendReminder(greetingId, req.name) } // Respond to caller return GreetingResponse("You said $greetingPrefix to ${req.name}!") diff --git a/kotlin/templates/kotlin-gradle-spring-boot/src/test/kotlin/com/example/restatestarter/GreeterTest.kt b/kotlin/templates/kotlin-gradle-spring-boot/src/test/kotlin/com/example/restatestarter/GreeterTest.kt index ba493c71..cd72f362 100644 --- a/kotlin/templates/kotlin-gradle-spring-boot/src/test/kotlin/com/example/restatestarter/GreeterTest.kt +++ b/kotlin/templates/kotlin-gradle-spring-boot/src/test/kotlin/com/example/restatestarter/GreeterTest.kt @@ -10,9 +10,11 @@ package com.example.restatestarter import com.example.restatestarter.Greeter.Greeting import dev.restate.client.Client +import dev.restate.client.kotlin.service import dev.restate.sdk.testing.BindService -import dev.restate.sdk.testing.RestateClient import dev.restate.sdk.testing.RestateTest +import dev.restate.sdk.testing.RestateURL +import dev.restate.serde.kotlinx.KotlinSerializationSerdeFactory import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -29,8 +31,10 @@ class GreeterTest { @Test @Timeout(value = 10) - fun greet(@RestateClient ingressClient: Client) = runTest { - val client = GreeterClient.fromClient(ingressClient) + fun greet(@RestateURL url: String) = runTest { + // Kotlin services (de)serialize with kotlinx.serialization, so build the client + // with the KotlinSerializationSerdeFactory. + val client = Client.connect(url, KotlinSerializationSerdeFactory()).service() assertThat(client.greet(Greeting("Francesco")).message).isEqualTo("You said ciao to Francesco!") } diff --git a/kotlin/templates/kotlin-gradle/build.gradle.kts b/kotlin/templates/kotlin-gradle/build.gradle.kts index c90f6503..a9432bb4 100644 --- a/kotlin/templates/kotlin-gradle/build.gradle.kts +++ b/kotlin/templates/kotlin-gradle/build.gradle.kts @@ -1,20 +1,17 @@ plugins { application - kotlin("jvm") version "2.2.10" - kotlin("plugin.serialization") version "2.2.10" - id("com.google.devtools.ksp") version "2.2.10-2.0.2" + kotlin("jvm") version "2.4.0" + kotlin("plugin.serialization") version "2.4.0" + kotlin("plugin.allopen") version "2.4.0" } repositories { mavenCentral() } -val restateVersion = "2.7.0" +val restateVersion = "2.9.0" dependencies { - // Annotation processor - ksp("dev.restate:sdk-api-kotlin-gen:$restateVersion") - // Restate SDK implementation("dev.restate:sdk-kotlin-http:$restateVersion") @@ -23,10 +20,19 @@ dependencies { } kotlin { - jvmToolchain(21) + jvmToolchain(25) +} + +allOpen { + // Restate proxy clients need non-final classes. + annotation("dev.restate.sdk.annotation.Service") + annotation("dev.restate.sdk.annotation.VirtualObject") + annotation("dev.restate.sdk.annotation.Workflow") } // Configure main class application { mainClass.set("my.example.GreeterKt") + // Java 25 warnings: --enable-native-access for the Restate SDK state machine, --sun-misc-unsafe-memory-access for netty. + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "--sun-misc-unsafe-memory-access=allow") } diff --git a/kotlin/templates/kotlin-gradle/gradle/wrapper/gradle-wrapper.jar b/kotlin/templates/kotlin-gradle/gradle/wrapper/gradle-wrapper.jar index 9bbc975c..b1b8ef56 100644 Binary files a/kotlin/templates/kotlin-gradle/gradle/wrapper/gradle-wrapper.jar and b/kotlin/templates/kotlin-gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/templates/kotlin-gradle/gradle/wrapper/gradle-wrapper.properties b/kotlin/templates/kotlin-gradle/gradle/wrapper/gradle-wrapper.properties index d4081da4..a9db1155 100644 --- a/kotlin/templates/kotlin-gradle/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/templates/kotlin-gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin/templates/kotlin-gradle/gradlew b/kotlin/templates/kotlin-gradle/gradlew index faf93008..249efbb0 100755 --- a/kotlin/templates/kotlin-gradle/gradlew +++ b/kotlin/templates/kotlin-gradle/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 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. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # 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" ) @@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/kotlin/templates/kotlin-gradle/gradlew.bat b/kotlin/templates/kotlin-gradle/gradlew.bat index 9b42019c..8508ef68 100644 --- a/kotlin/templates/kotlin-gradle/gradlew.bat +++ b/kotlin/templates/kotlin-gradle/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -: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 +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/kotlin/templates/kotlin-gradle/src/main/kotlin/my/example/Greeter.kt b/kotlin/templates/kotlin-gradle/src/main/kotlin/my/example/Greeter.kt index 654f05b3..f3fd5357 100644 --- a/kotlin/templates/kotlin-gradle/src/main/kotlin/my/example/Greeter.kt +++ b/kotlin/templates/kotlin-gradle/src/main/kotlin/my/example/Greeter.kt @@ -17,12 +17,12 @@ class Greeter { data class GreetingResponse(val message: String) @Handler - suspend fun greet(ctx: Context, req: Greeting): GreetingResponse { + suspend fun greet(req: Greeting): GreetingResponse { // Durably execute a set of steps; resilient against failures - val greetingId = ctx.random().nextUUID().toString() - ctx.runBlock("Notification") { sendNotification(greetingId, req.name) } - ctx.sleep(1.seconds) - ctx.runBlock("Reminder") { sendReminder(greetingId, req.name) } + val greetingId = random().nextUUID().toString() + runBlock("Notification") { sendNotification(greetingId, req.name) } + sleep(1.seconds) + runBlock("Reminder") { sendReminder(greetingId, req.name) } // Respond to caller return GreetingResponse("You said hi to ${req.name}!")