diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index aab88d9d3..2168d48d8 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 57f265217..edc704f77 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 d670f78e1..c2d4bf8ed 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 9d7e76a91..33a928f20 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 cb9b3e66b..161454ee6 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 c63a72cfd..d287c183d 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 c032bd24a..4c77fe2de 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9b42019c7..8508ef684 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 39673f6d7..a663a3495 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 869befa83..e0d7e06a3 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 db31bea4e..6a087f6c6 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 2559ec563..6f78e4fab 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 fe840a9f4..837516989 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 43d91a3f5..73ee9c083 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 1b33c55ba..b1b8ef56b 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 d4081da47..a9db11550 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 23d15a936..249efbb03 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 5eed7ee84..8508ef684 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 8efad182d..5c9fcb883 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 f40f96ef5..62ed1eca7 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 690eb81e4..58ef06806 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 42cee583b..b9bae3b06 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 b9818402e..ecd5a315e 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 804fe18eb..9f73f5eb2 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 5049b997a..0cfe61b5b 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 1200eec83..74913b640 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 6b32ff4de..a542c267c 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 8696d5767..d9ecc4941 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 313c460c5..76c9a9e56 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 1b33c55ba..b1b8ef56b 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 d4081da47..a9db11550 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 23d15a936..249efbb03 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 5eed7ee84..8508ef684 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 937be773f..de310c3bd 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 09e33b3ca..56d613cc6 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 d58dfb70b..216df0589 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 19529ddf8..bd8896bf2 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 249bdf382..92450f932 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 d130f25ef..551963a8d 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 0ce017a14..061c9d39e 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 fe09cc436..42fdf3bb7 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 858afa09c..df65cc233 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 bf2793525..b909410e8 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 c6ea1e007..5da78dddd 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 6977fc106..0c7ecdcf5 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 033e24c4c..b1b8ef56b 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 a80b22ce5..a9db11550 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 fcb6fca14..249efbb03 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 93e3f59f1..8508ef684 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 dda30ced2..7ee68d3b9 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 e5b23feb0..d8319f49c 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 e6441136f..b1b8ef56b 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 37f853b1c..a9db11550 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 b740cf133..249efbb03 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 7101f8e46..8508ef684 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 361d5fb5e..deaf04854 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 6b2a85c71..659a39559 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9b42019c7..8508ef684 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 f25994352..679074f6e 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 e40a6de10..85f19bb3f 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 10e372ad6..9c1065d30 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 4ddd30add..705482af8 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 04a3ed2f6..47be62068 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 95da77ac1..cfbec19dc 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 8b0bc6521..147100c65 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 6c83c099f..57aa563aa 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 e52210a16..18c1335b8 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 a575c7c66..4b83e56c5 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 9ae436086..b4f273241 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 bcf3ff1cb..5997c2d5f 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 0f727f49a..18629fc41 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 b6b6e22a2..1d03950b6 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 6011d5ba1..ba93ef427 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 42064240f..3a1d3fc28 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 fbe75a86e..7c2283dcb 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 81c3fafe9..d5df8635a 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 663ececd6..41736cf2b 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 b788656e9..2ba322b00 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 269d21c5d..fda3fc9b2 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 42a8aa9b2..f270a17b2 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 d6bef3b2f..444cff37f 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 e3b3a4705..a0c12c129 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 1b33c55ba..b1b8ef56b 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 37f78a6af..a9db11550 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 23d15a936..249efbb03 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 5eed7ee84..8508ef684 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 fcb46da0b..663f19e31 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 e72e54b74..bc473ca7b 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 fe7d037de..7e9c1e3c9 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 72df6c617..599ac34a0 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 5e9618cac..1ddd97b9e 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 4136715f0..c453424c6 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 284bd8b21..d4de689ab 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 31ddc6cf4..7b60ee680 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 d58dfb70b..216df0589 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 1426e395a..e512d5db5 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 19529ddf8..bd8896bf2 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 249bdf382..92450f932 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 d5feb1114..9c0c44a33 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 bbc24a062..63de5f5ca 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 9bfca0ff3..45588de35 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 0a3408f17..54696d990 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 d58dfb70b..216df0589 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 714e28378..370b8abca 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 19529ddf8..bd8896bf2 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 b150b91ed..5761d9489 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 c9e17ac73..9de37fbe7 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 9efbb8258..ccf9eff34 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 c3e423244..947753347 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 b9c8574c2..000000000 --- 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 78a21c5ab..000000000 --- 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 599b1e289..000000000 --- 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 1c69c4f5f..000000000 --- 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 135eaf775..000000000 --- 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 444cff37f..000000000 --- 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 bdf3bbbe9..000000000 --- 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 1b33c55ba..000000000 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 37f78a6af..000000000 --- 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 23d15a936..000000000 --- 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 5eed7ee84..000000000 --- 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 778b3b8ec..000000000 --- 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 663f19e31..000000000 --- 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 7e7138d74..000000000 --- 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 536d1d39a..000000000 --- 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 bc473ca7b..000000000 --- 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 b9c8574c2..000000000 --- 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 78a21c5ab..000000000 --- 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 599b1e289..000000000 --- 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 1c69c4f5f..000000000 --- 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 3b41682ac..000000000 --- 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 549e00a2a..000000000 --- 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 d58dfb70b..000000000 --- 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 e512d5db5..000000000 --- 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 19529ddf8..000000000 --- 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 249bdf382..000000000 --- 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 2d521a09f..000000000 --- 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 628cc823c..000000000 --- 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 3ebdb0444..000000000 --- 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 9bc8f2b07..000000000 --- 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 92d8c8d30..000000000 --- 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 0372747da..000000000 --- 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 54696d990..000000000 --- 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 b9c8574c2..000000000 --- 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 78a21c5ab..000000000 --- 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 599b1e289..000000000 --- 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 1c69c4f5f..000000000 --- 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 1a05f502f..000000000 --- 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 d58dfb70b..000000000 --- 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 16c2c415d..000000000 --- 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 19529ddf8..000000000 --- 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 b150b91ed..000000000 --- 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 b9ca55d3c..000000000 --- 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 e265fe69a..000000000 --- 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 7e7138d74..000000000 --- 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 536d1d39a..000000000 --- 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 947753347..000000000 --- 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 516d321c2..330851746 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9b42019c7..8508ef684 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 05a2eb693..4245c001e 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 22d16c29a..4152ad68d 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 039fbda78..b66c88c87 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 ebece1f2a..5eb6f1c03 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 9c1ac9431..032206f4d 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 de46b1d06..20820748c 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 62877beb0..81cef0b90 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 4b98934e4..ed280515f 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 a86bb65bb..916d95527 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 e3167f980..edb6bfa72 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9b42019c7..8508ef684 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 51414d35b..d9a87352b 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 93432dc1d..3e85ea9a5 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 907566b7e..d6bb9f379 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 ccbeb6a14..9a3950fd1 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 32129177c..c6ee61f41 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 124449864..2735f15ce 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 286ccd1c7..4a412e9b8 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 6c7bf6733..334158c29 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 430160f3b..ffd84b231 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 cf711ccc3..1064cf526 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 02663b4d1..8ef488009 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9b42019c7..8508ef684 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 4ec245cb9..810cf3dc3 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 5d2804e63..99c1463b4 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 335a4693b..837599d1a 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 4303ebbe6..c88f43170 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 d64a1ea09..8357083cf 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 38a48d36a..bd4e0d095 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 d380b8311..74037be9b 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9b42019c7..8508ef684 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 cf64f7db3..3b9d7c8dc 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 7bce9cb03..e905ae6c3 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 162837016..34a23a177 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 ef4cff522..0830a182a 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 16a239c78..0f5e0c8da 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 cbd78843e..d6521c633 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 483c56920..bc3cbb459 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 5e2cb168b..89a7e7907 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 0d074d32b..968c29889 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 2c5979330..7849cb973 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 8ac0d9a2a..8021bef21 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 1598741fc..b74fbc8ec 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 f3445a734..43945cd35 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 d3b9bb7d7..7f5a01721 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 dbb42bef6..761e4d8d8 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 85c5c1e7c..9b6a02017 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 d2da6ca50..ed0223b98 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 b621f9ae0..203093d50 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 f39198bf5..c5f3bf9c8 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 033e24c4c..b1b8ef56b 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 37f853b1c..a9db11550 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 fcb6fca14..249efbb03 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 6689b85be..8508ef684 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 824de76db..e4dbcee6f 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 d6414461d..9e0939cac 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 d951fe8b4..b120d1b87 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9b42019c7..8508ef684 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 6a7aded18..3901bd524 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 31c5ed9e2..8b9c92d99 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 9e725a736..366d0e3c6 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 b0591edd4..10c25acdd 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 57fa012f3..7eb491dfa 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 60429a06c..2047305d3 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 239cbda83..de091403b 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 8e1cb20d6..ae1f73d26 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 62909953b..f25b739c1 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 c1604c8e5..a4d8c20f4 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 054b66267..8ead032a5 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9d21a2183..a51ec4f58 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 a79c40d0f..4e6a43305 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 ba493c71c..cd72f362d 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 c90f6503e..a9432bb4f 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 9bbc975c7..b1b8ef56b 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 d4081da47..a9db11550 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 faf93008b..249efbb03 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 9b42019c7..8508ef684 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 654f05b3b..f3fd53572 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}!")