From 45c404a421551e330db20a9ff1a6b238c7fa9b09 Mon Sep 17 00:00:00 2001 From: Till Rohrmann Date: Thu, 16 May 2024 21:09:55 +0200 Subject: [PATCH 1/6] Squashed 'sdk-core/src/main/service-protocol/' changes from 552361e..1898426 1898426 Update min/max of protocol version fields in endpoint_manifest_schema 08871e0 Introduce service.Version and discovery.Version enums 2f3e461 Workflow api changes (#91) 1fa71a5 Add versioning info (#90) git-subtree-dir: sdk-core/src/main/service-protocol git-subtree-split: 1898426fc98c16d704068594cd54394912845ff7 --- .gitattributes | 6 - .github/workflows/bump.yaml | 30 - .github/workflows/ci.yml | 64 -- .../.github => .github}/workflows/lint.yaml | 0 .github/workflows/manual-release.yml | 9 - .github/workflows/publish-test-results.yml | 38 - .github/workflows/push-release.yml | 13 - .github/workflows/release.yml | 58 -- .gitignore | 37 +- .ignore | 1 - .../.prettierrc.toml => .prettierrc.toml | 0 .../.protolint.yaml => .protolint.yaml | 0 LICENSE | 34 +- README.md | 447 +--------- admin-client/build.gradle.kts | 56 -- admin-client/src/main/openapi/meta.json | 1 - build.gradle.kts | 124 --- buildSrc/build.gradle.kts | 7 - .../library-publishing-conventions.gradle.kts | 56 -- config/allowed-licenses.json | 25 - config/license-header | 8 - config/license-normalizer-bundle.json | 35 - dev/restate/service/discovery.proto | 22 + .../restate/service/protocol.proto | 63 +- development/README.md | 7 - development/developer-guidelines.md | 5 - development/release.md | 11 - ...hema.json => endpoint_manifest_schema.json | 20 +- examples/README.md | 52 -- examples/build.gradle.kts | 42 - .../java/my/restate/sdk/examples/Counter.java | 79 -- .../restate/sdk/examples/LambdaHandler.java | 34 - .../my/restate/sdk/examples/LoanWorkflow.java | 247 ------ .../my/restate/sdk/examples/CounterKt.kt | 62 -- examples/src/main/resources/log4j2.properties | 26 - gradle.properties | 5 - gradle/libs.versions.toml | 2 - gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 7 - gradlew | 249 ------ gradlew.bat | 92 --- sdk-api-gen-common/build.gradle.kts | 13 - .../dev/restate/sdk/gen/model/Handler.java | 102 --- .../restate/sdk/gen/model/HandlerType.java | 16 - .../restate/sdk/gen/model/PayloadType.java | 75 -- .../dev/restate/sdk/gen/model/Service.java | 155 ---- .../template/HandlebarsTemplateEngine.java | 158 ---- sdk-api-gen/build.gradle.kts | 41 - .../dev/restate/sdk/gen/ElementConverter.java | 341 -------- .../restate/sdk/gen/FilerTemplateLoader.java | 57 -- .../dev/restate/sdk/gen/ServiceProcessor.java | 158 ---- .../javax.annotation.processing.Processor | 1 - .../resources/templates/BindableService.hbs | 39 - .../templates/BindableServiceFactory.hbs | 16 - .../src/main/resources/templates/Client.hbs | 167 ---- .../templates/workflow/BindableService.hbs | 51 -- .../resources/templates/workflow/Client.hbs | 148 ---- .../java/dev/restate/sdk/CodegenTest.java | 179 ---- .../restate/sdk/GreeterWithExplicitName.java | 18 - .../sdk/GreeterWithoutExplicitName.java | 18 - .../dev/restate/sdk/JavaCodegenTests.java | 29 - .../dev/restate/sdk/NameInferenceTest.java | 24 - sdk-api-kotlin-gen/build.gradle.kts | 40 - .../sdk/kotlin/gen/KElementConverter.kt | 243 ------ .../sdk/kotlin/gen/ServiceProcessor.kt | 144 ---- .../kotlin/gen/ServiceProcessorProvider.kt | 20 - ...ols.ksp.processing.SymbolProcessorProvider | 1 - .../resources/templates/BindableService.hbs | 27 - .../templates/BindableServiceFactory.hbs | 18 - .../src/main/resources/templates/Client.hbs | 87 -- .../dev/restate/sdk/kotlin/CodegenTest.kt | 170 ---- .../dev/restate/sdk/kotlin/KtCodegenTests.kt | 26 - sdk-api-kotlin/build.gradle.kts | 37 - .../dev/restate/sdk/kotlin/Awaitables.kt | 191 ----- .../dev/restate/sdk/kotlin/ContextImpl.kt | 225 ----- .../kotlin/dev/restate/sdk/kotlin/KtSerdes.kt | 70 -- .../kotlin/dev/restate/sdk/kotlin/Service.kt | 193 ----- .../kotlin/dev/restate/sdk/kotlin/Util.kt | 54 -- .../main/kotlin/dev/restate/sdk/kotlin/api.kt | 446 ---------- .../kotlin/dev/restate/sdk/kotlin/ingress.kt | 47 -- .../dev/restate/sdk/kotlin/AwakeableIdTest.kt | 22 - .../dev/restate/sdk/kotlin/DeferredTest.kt | 107 --- .../dev/restate/sdk/kotlin/EagerStateTest.kt | 60 -- .../restate/sdk/kotlin/InvocationIdTest.kt | 21 - .../sdk/kotlin/KotlinCoroutinesTests.kt | 67 -- .../sdk/kotlin/OnlyInputAndOutputTest.kt | 19 - .../dev/restate/sdk/kotlin/RandomTest.kt | 31 - .../dev/restate/sdk/kotlin/SideEffectTest.kt | 68 -- .../dev/restate/sdk/kotlin/SleepTest.kt | 32 - .../sdk/kotlin/StateMachineFailuresTest.kt | 53 -- .../dev/restate/sdk/kotlin/StateTest.kt | 80 -- .../restate/sdk/kotlin/UserFailuresTest.kt | 50 -- sdk-api/build.gradle.kts | 35 - .../java/dev/restate/sdk/AnyAwaitable.java | 33 - .../main/java/dev/restate/sdk/Awaitable.java | 185 ----- .../main/java/dev/restate/sdk/Awakeable.java | 55 -- .../java/dev/restate/sdk/AwakeableHandle.java | 33 - .../main/java/dev/restate/sdk/Context.java | 195 ----- .../java/dev/restate/sdk/ContextImpl.java | 218 ----- .../java/dev/restate/sdk/ObjectContext.java | 42 - .../java/dev/restate/sdk/RestateRandom.java | 65 -- .../main/java/dev/restate/sdk/Service.java | 252 ------ .../dev/restate/sdk/SharedObjectContext.java | 48 -- .../src/main/java/dev/restate/sdk/Util.java | 89 -- .../java/dev/restate/sdk/AwakeableIdTest.java | 26 - .../java/dev/restate/sdk/DeferredTest.java | 153 ---- .../java/dev/restate/sdk/EagerStateTest.java | 88 -- .../dev/restate/sdk/InvocationIdTest.java | 26 - .../dev/restate/sdk/JavaBlockingTests.java | 70 -- .../restate/sdk/OnlyInputAndOutputTest.java | 26 - .../test/java/dev/restate/sdk/RandomTest.java | 42 - .../java/dev/restate/sdk/SideEffectTest.java | 110 --- .../test/java/dev/restate/sdk/SleepTest.java | 56 -- .../restate/sdk/StateMachineFailuresTest.java | 62 -- .../test/java/dev/restate/sdk/StateTest.java | 68 -- .../dev/restate/sdk/UserFailuresTest.java | 82 -- sdk-common/build.gradle.kts | 82 -- .../dev/restate/sdk/annotation/Exclusive.java | 18 - .../dev/restate/sdk/annotation/Handler.java | 18 - .../dev/restate/sdk/annotation/Service.java | 23 - .../dev/restate/sdk/annotation/Shared.java | 18 - .../restate/sdk/annotation/VirtualObject.java | 24 - .../dev/restate/sdk/annotation/Workflow.java | 24 - .../sdk/client/DefaultIngressClient.java | 250 ------ .../dev/restate/sdk/client/IngressClient.java | 147 ---- .../restate/sdk/client/IngressException.java | 54 -- .../restate/sdk/client/RequestOptions.java | 73 -- .../sdk/common/AbortedExecutionException.java | 25 - .../restate/sdk/common/BindableService.java | 20 - .../sdk/common/BindableServiceFactory.java | 16 - .../dev/restate/sdk/common/CoreSerdes.java | 181 ---- .../dev/restate/sdk/common/HandlerType.java | 14 - .../dev/restate/sdk/common/InvocationId.java | 26 - .../java/dev/restate/sdk/common/Request.java | 74 -- .../java/dev/restate/sdk/common/Serde.java | 62 -- .../dev/restate/sdk/common/ServiceType.java | 15 - .../java/dev/restate/sdk/common/StateKey.java | 49 -- .../java/dev/restate/sdk/common/Target.java | 67 -- .../restate/sdk/common/TerminalException.java | 55 -- .../common/function/ThrowingBiConsumer.java | 41 - .../sdk/common/function/ThrowingFunction.java | 42 - .../sdk/common/function/ThrowingRunnable.java | 17 - .../sdk/common/function/ThrowingSupplier.java | 21 - .../restate/sdk/common/syscalls/Deferred.java | 29 - .../EnterSideEffectSyscallCallback.java | 14 - .../ExitSideEffectSyscallCallback.java | 18 - .../common/syscalls/HandlerDefinition.java | 69 -- .../common/syscalls/InvocationHandler.java | 25 - .../restate/sdk/common/syscalls/Result.java | 175 ---- .../common/syscalls/ServiceDefinition.java | 63 -- .../sdk/common/syscalls/SyscallCallback.java | 74 -- .../restate/sdk/common/syscalls/Syscalls.java | 93 --- .../restate/sdk/common/CoreSerdesTest.java | 81 -- sdk-core/build.gradle.kts | 81 -- .../BaseSuspendableCallbackStateMachine.java | 62 -- .../dev/restate/sdk/core/CallbackHandle.java | 46 -- .../dev/restate/sdk/core/DeferredResults.java | 255 ------ .../restate/sdk/core/DeploymentManifest.java | 74 -- .../java/dev/restate/sdk/core/Entries.java | 534 ------------ ...tionCatchingInvocationInputSubscriber.java | 52 -- .../sdk/core/ExecutorSwitchingSyscalls.java | 167 ---- .../sdk/core/IncomingEntriesStateMachine.java | 54 -- .../restate/sdk/core/InputPublisherState.java | 33 - .../dev/restate/sdk/core/InvocationFlow.java | 53 -- .../restate/sdk/core/InvocationIdImpl.java | 71 -- .../dev/restate/sdk/core/InvocationState.java | 16 - .../sdk/core/InvocationStateMachine.java | 771 ------------------ .../dev/restate/sdk/core/MessageHeader.java | 120 --- .../dev/restate/sdk/core/MessageType.java | 247 ------ .../restate/sdk/core/ProtocolException.java | 83 -- .../sdk/core/ReadyResultStateMachine.java | 96 --- .../sdk/core/ResolvedEndpointHandler.java | 24 - .../sdk/core/ResolvedEndpointHandlerImpl.java | 122 --- .../sdk/core/RestateContextDataProvider.java | 46 -- .../dev/restate/sdk/core/RestateEndpoint.java | 210 ----- .../sdk/core/SideEffectAckStateMachine.java | 57 -- .../restate/sdk/core/SuspendableCallback.java | 16 - .../dev/restate/sdk/core/SyscallsImpl.java | 368 --------- .../restate/sdk/core/SyscallsInternal.java | 43 - .../java/dev/restate/sdk/core/Tracing.java | 28 - .../dev/restate/sdk/core/UserStateStore.java | 80 -- .../main/java/dev/restate/sdk/core/Util.java | 159 ---- ...ogging.log4j.core.util.ContextDataProvider | 1 - .../main/sdk-proto/dev/restate/sdk/java.proto | 24 - sdk-core/src/main/service-protocol/.gitignore | 2 - sdk-core/src/main/service-protocol/LICENSE | 21 - sdk-core/src/main/service-protocol/README.md | 13 - .../dev/restate/sdk/core/AssertUtils.java | 60 -- .../sdk/core/AwakeableIdTestSuite.java | 71 -- .../core/ComponentDiscoveryHandlerTest.java | 46 -- .../restate/sdk/core/DeferredTestSuite.java | 389 --------- .../restate/sdk/core/EagerStateTestSuite.java | 170 ---- .../java/dev/restate/sdk/core/FlowUtils.java | 193 ----- .../sdk/core/InvocationIdTestSuite.java | 38 - .../restate/sdk/core/MessageHeaderTest.java | 40 - .../restate/sdk/core/MockMultiThreaded.java | 91 --- .../restate/sdk/core/MockSingleThread.java | 81 -- .../sdk/core/OnlyInputAndOutputTestSuite.java | 29 - .../java/dev/restate/sdk/core/ProtoUtils.java | 258 ------ .../dev/restate/sdk/core/RandomTestSuite.java | 43 - .../restate/sdk/core/SideEffectTestSuite.java | 129 --- .../dev/restate/sdk/core/SleepTestSuite.java | 113 --- .../core/StateMachineFailuresTestSuite.java | 87 -- .../dev/restate/sdk/core/StateTestSuite.java | 114 --- .../dev/restate/sdk/core/TestDefinitions.java | 282 ------- .../java/dev/restate/sdk/core/TestRunner.java | 102 --- .../sdk/core/UserFailuresTestSuite.java | 88 -- .../test/resources/junit-platform.properties | 3 - sdk-core/src/test/resources/log4j2.properties | 8 - sdk-http-vertx/build.gradle.kts | 49 -- .../http/vertx/HttpRequestFlowAdapter.java | 110 --- .../http/vertx/HttpResponseFlowAdapter.java | 106 --- .../sdk/http/vertx/MessageDecoder.java | 101 --- .../sdk/http/vertx/MessageEncoder.java | 29 - .../http/vertx/RequestHttpServerHandler.java | 172 ---- .../vertx/RestateHttpEndpointBuilder.java | 147 ---- ...ogging.log4j.core.util.ContextDataProvider | 1 - .../vertx/testservices/BlockingGreeter.java | 37 - .../sdk/http/vertx/HttpVertxTestExecutor.kt | 100 --- .../restate/sdk/http/vertx/HttpVertxTests.kt | 47 -- .../sdk/http/vertx/RestateHttpEndpointTest.kt | 230 ------ .../sdk/http/vertx/VertxExecutorsTest.kt | 97 --- .../vertx/testservices/GreeterKtComponent.kt | 32 - .../test/resources/junit-platform.properties | 3 - sdk-lambda/build.gradle.kts | 38 - .../sdk/lambda/BaseRestateLambdaHandler.java | 51 -- .../sdk/lambda/LambdaFlowAdapters.java | 169 ---- .../sdk/lambda/RestateLambdaEndpoint.java | 200 ----- .../lambda/RestateLambdaEndpointBuilder.java | 56 -- .../restate/sdk/lambda/LambdaHandlerTest.java | 172 ---- .../testservices/JavaCounterService.java | 32 - .../testservices/MyServicesHandler.java | 19 - .../testservices/KotlinCounterService.kt | 27 - .../src/test/resources/log4j2.properties | 8 - sdk-serde-jackson/build.gradle.kts | 19 - .../sdk/serde/jackson/JacksonSerdes.java | 125 --- .../sdk/serde/jackson/JacksonSerdesTest.java | 75 -- sdk-serde-protobuf/build.gradle.kts | 14 - .../sdk/serde/protobuf/ProtobufSerdes.java | 56 -- sdk-testing/build.gradle.kts | 25 - .../sdk/testing/BaseRestateRunner.java | 64 -- .../sdk/testing/ManualRestateRunner.java | 186 ----- .../sdk/testing/RestateAdminClient.java | 22 - .../sdk/testing/RestateIngressClient.java | 19 - .../restate/sdk/testing/RestateRunner.java | 53 -- .../sdk/testing/RestateRunnerBuilder.java | 95 --- .../dev/restate/sdk/testing/RestateURL.java | 23 - .../java/dev/restate/sdk/testing/Counter.java | 71 -- .../dev/restate/sdk/testing/CounterTest.java | 36 - sdk-testing/src/test/proto/counter.proto | 45 - .../src/test/resources/log4j2.properties | 18 - sdk-workflow-api/build.gradle.kts | 54 -- .../restate/sdk/workflow/DurablePromise.java | 20 - .../sdk/workflow/DurablePromiseHandle.java | 15 - .../sdk/workflow/DurablePromiseKey.java | 52 -- .../restate/sdk/workflow/WorkflowBuilder.java | 46 -- .../restate/sdk/workflow/WorkflowContext.java | 19 - .../sdk/workflow/WorkflowExecutionState.java | 15 - .../sdk/workflow/WorkflowSharedContext.java | 26 - .../sdk/workflow/impl/InvokeRequest.java | 59 -- .../workflow/impl/WorkflowCodegenUtil.java | 241 ------ .../workflow/impl/WorkflowContextImpl.java | 235 ------ .../sdk/workflow/impl/WorkflowImpl.java | 419 ---------- .../dev/restate/sdk/workflow/workflow.proto | 79 -- ...tocol.md => service-invocation-protocol.md | 64 +- settings.gradle.kts | 113 --- 266 files changed, 179 insertions(+), 21406 deletions(-) delete mode 100644 .gitattributes delete mode 100644 .github/workflows/bump.yaml delete mode 100644 .github/workflows/ci.yml rename {sdk-core/src/main/service-protocol/.github => .github}/workflows/lint.yaml (100%) delete mode 100644 .github/workflows/manual-release.yml delete mode 100644 .github/workflows/publish-test-results.yml delete mode 100644 .github/workflows/push-release.yml delete mode 100644 .github/workflows/release.yml delete mode 120000 .ignore rename sdk-core/src/main/service-protocol/.prettierrc.toml => .prettierrc.toml (100%) rename sdk-core/src/main/service-protocol/.protolint.yaml => .protolint.yaml (100%) delete mode 100644 admin-client/build.gradle.kts delete mode 100644 admin-client/src/main/openapi/meta.json delete mode 100644 build.gradle.kts delete mode 100644 buildSrc/build.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/library-publishing-conventions.gradle.kts delete mode 100644 config/allowed-licenses.json delete mode 100644 config/license-header delete mode 100644 config/license-normalizer-bundle.json create mode 100644 dev/restate/service/discovery.proto rename {sdk-core/src/main/service-protocol/dev => dev}/restate/service/protocol.proto (88%) delete mode 100644 development/README.md delete mode 100644 development/developer-guidelines.md delete mode 100644 development/release.md rename sdk-core/src/main/service-protocol/deployment_manifest_schema.json => endpoint_manifest_schema.json (89%) delete mode 100644 examples/README.md delete mode 100644 examples/build.gradle.kts delete mode 100644 examples/src/main/java/my/restate/sdk/examples/Counter.java delete mode 100644 examples/src/main/java/my/restate/sdk/examples/LambdaHandler.java delete mode 100644 examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java delete mode 100644 examples/src/main/kotlin/my/restate/sdk/examples/CounterKt.kt delete mode 100644 examples/src/main/resources/log4j2.properties delete mode 100644 gradle.properties delete mode 100644 gradle/libs.versions.toml delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat delete mode 100644 sdk-api-gen-common/build.gradle.kts delete mode 100644 sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java delete mode 100644 sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/HandlerType.java delete mode 100644 sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/PayloadType.java delete mode 100644 sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Service.java delete mode 100644 sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java delete mode 100644 sdk-api-gen/build.gradle.kts delete mode 100644 sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java delete mode 100644 sdk-api-gen/src/main/java/dev/restate/sdk/gen/FilerTemplateLoader.java delete mode 100644 sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java delete mode 100644 sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor delete mode 100644 sdk-api-gen/src/main/resources/templates/BindableService.hbs delete mode 100644 sdk-api-gen/src/main/resources/templates/BindableServiceFactory.hbs delete mode 100644 sdk-api-gen/src/main/resources/templates/Client.hbs delete mode 100644 sdk-api-gen/src/main/resources/templates/workflow/BindableService.hbs delete mode 100644 sdk-api-gen/src/main/resources/templates/workflow/Client.hbs delete mode 100644 sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java delete mode 100644 sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithExplicitName.java delete mode 100644 sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithoutExplicitName.java delete mode 100644 sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java delete mode 100644 sdk-api-gen/src/test/java/dev/restate/sdk/NameInferenceTest.java delete mode 100644 sdk-api-kotlin-gen/build.gradle.kts delete mode 100644 sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt delete mode 100644 sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt delete mode 100644 sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessorProvider.kt delete mode 100644 sdk-api-kotlin-gen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider delete mode 100644 sdk-api-kotlin-gen/src/main/resources/templates/BindableService.hbs delete mode 100644 sdk-api-kotlin-gen/src/main/resources/templates/BindableServiceFactory.hbs delete mode 100644 sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs delete mode 100644 sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt delete mode 100644 sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/KtCodegenTests.kt delete mode 100644 sdk-api-kotlin/build.gradle.kts delete mode 100644 sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Awaitables.kt delete mode 100644 sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt delete mode 100644 sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/KtSerdes.kt delete mode 100644 sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Service.kt delete mode 100644 sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Util.kt delete mode 100644 sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt delete mode 100644 sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ingress.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/AwakeableIdTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/DeferredTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/EagerStateTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/InvocationIdTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/KotlinCoroutinesTests.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/OnlyInputAndOutputTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/RandomTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SideEffectTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/SleepTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateMachineFailuresTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/StateTest.kt delete mode 100644 sdk-api-kotlin/src/test/kotlin/dev/restate/sdk/kotlin/UserFailuresTest.kt delete mode 100644 sdk-api/build.gradle.kts delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/AnyAwaitable.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/Awaitable.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/Awakeable.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/AwakeableHandle.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/Context.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/ContextImpl.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/ObjectContext.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/RestateRandom.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/Service.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/SharedObjectContext.java delete mode 100644 sdk-api/src/main/java/dev/restate/sdk/Util.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/DeferredTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/JavaBlockingTests.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/RandomTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/SleepTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/StateTest.java delete mode 100644 sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java delete mode 100644 sdk-common/build.gradle.kts delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/client/DefaultIngressClient.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/client/IngressClient.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/client/IngressException.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/client/RequestOptions.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/AbortedExecutionException.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/BindableService.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/BindableServiceFactory.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/CoreSerdes.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/HandlerType.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/InvocationId.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/Request.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/Serde.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/ServiceType.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/StateKey.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/Target.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/TerminalException.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingBiConsumer.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingFunction.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingRunnable.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingSupplier.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Deferred.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/EnterSideEffectSyscallCallback.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExitSideEffectSyscallCallback.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/HandlerDefinition.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Result.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ServiceDefinition.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/SyscallCallback.java delete mode 100644 sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Syscalls.java delete mode 100644 sdk-common/src/test/java/dev/restate/sdk/common/CoreSerdesTest.java delete mode 100644 sdk-core/build.gradle.kts delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/BaseSuspendableCallbackStateMachine.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/CallbackHandle.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/DeferredResults.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/Entries.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/ExceptionCatchingInvocationInputSubscriber.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/ExecutorSwitchingSyscalls.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/IncomingEntriesStateMachine.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/InputPublisherState.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/InvocationFlow.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/InvocationIdImpl.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/InvocationState.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/InvocationStateMachine.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/MessageHeader.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/MessageType.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/ProtocolException.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/ReadyResultStateMachine.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandler.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandlerImpl.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/RestateContextDataProvider.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/SideEffectAckStateMachine.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/SuspendableCallback.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/SyscallsImpl.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/SyscallsInternal.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/Tracing.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/UserStateStore.java delete mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/Util.java delete mode 100644 sdk-core/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider delete mode 100644 sdk-core/src/main/sdk-proto/dev/restate/sdk/java.proto delete mode 100644 sdk-core/src/main/service-protocol/.gitignore delete mode 100644 sdk-core/src/main/service-protocol/LICENSE delete mode 100644 sdk-core/src/main/service-protocol/README.md delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/AssertUtils.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/AwakeableIdTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/DeferredTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/EagerStateTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/FlowUtils.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/InvocationIdTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/MockMultiThreaded.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/MockSingleThread.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/OnlyInputAndOutputTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/ProtoUtils.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/RandomTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/SideEffectTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/SleepTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/StateMachineFailuresTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/StateTestSuite.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/TestDefinitions.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/TestRunner.java delete mode 100644 sdk-core/src/test/java/dev/restate/sdk/core/UserFailuresTestSuite.java delete mode 100644 sdk-core/src/test/resources/junit-platform.properties delete mode 100644 sdk-core/src/test/resources/log4j2.properties delete mode 100644 sdk-http-vertx/build.gradle.kts delete mode 100644 sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpRequestFlowAdapter.java delete mode 100644 sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/HttpResponseFlowAdapter.java delete mode 100644 sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/MessageDecoder.java delete mode 100644 sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/MessageEncoder.java delete mode 100644 sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java delete mode 100644 sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java delete mode 100644 sdk-http-vertx/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider delete mode 100644 sdk-http-vertx/src/test/java/dev/restate/sdk/http/vertx/testservices/BlockingGreeter.java delete mode 100644 sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt delete mode 100644 sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTests.kt delete mode 100644 sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt delete mode 100644 sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/VertxExecutorsTest.kt delete mode 100644 sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/testservices/GreeterKtComponent.kt delete mode 100644 sdk-http-vertx/src/test/resources/junit-platform.properties delete mode 100644 sdk-lambda/build.gradle.kts delete mode 100644 sdk-lambda/src/main/java/dev/restate/sdk/lambda/BaseRestateLambdaHandler.java delete mode 100644 sdk-lambda/src/main/java/dev/restate/sdk/lambda/LambdaFlowAdapters.java delete mode 100644 sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java delete mode 100644 sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java delete mode 100644 sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java delete mode 100644 sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/JavaCounterService.java delete mode 100644 sdk-lambda/src/test/java/dev/restate/sdk/lambda/testservices/MyServicesHandler.java delete mode 100644 sdk-lambda/src/test/kotlin/dev/restate/sdk/lambda/testservices/KotlinCounterService.kt delete mode 100644 sdk-lambda/src/test/resources/log4j2.properties delete mode 100644 sdk-serde-jackson/build.gradle.kts delete mode 100644 sdk-serde-jackson/src/main/java/dev/restate/sdk/serde/jackson/JacksonSerdes.java delete mode 100644 sdk-serde-jackson/src/test/java/dev/restate/sdk/serde/jackson/JacksonSerdesTest.java delete mode 100644 sdk-serde-protobuf/build.gradle.kts delete mode 100644 sdk-serde-protobuf/src/main/java/dev/restate/sdk/serde/protobuf/ProtobufSerdes.java delete mode 100644 sdk-testing/build.gradle.kts delete mode 100644 sdk-testing/src/main/java/dev/restate/sdk/testing/BaseRestateRunner.java delete mode 100644 sdk-testing/src/main/java/dev/restate/sdk/testing/ManualRestateRunner.java delete mode 100644 sdk-testing/src/main/java/dev/restate/sdk/testing/RestateAdminClient.java delete mode 100644 sdk-testing/src/main/java/dev/restate/sdk/testing/RestateIngressClient.java delete mode 100644 sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunner.java delete mode 100644 sdk-testing/src/main/java/dev/restate/sdk/testing/RestateRunnerBuilder.java delete mode 100644 sdk-testing/src/main/java/dev/restate/sdk/testing/RestateURL.java delete mode 100644 sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java delete mode 100644 sdk-testing/src/test/java/dev/restate/sdk/testing/CounterTest.java delete mode 100644 sdk-testing/src/test/proto/counter.proto delete mode 100644 sdk-testing/src/test/resources/log4j2.properties delete mode 100644 sdk-workflow-api/build.gradle.kts delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromise.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseHandle.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseKey.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowBuilder.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowContext.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowExecutionState.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowSharedContext.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/InvokeRequest.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java delete mode 100644 sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java delete mode 100644 sdk-workflow-api/src/main/proto/dev/restate/sdk/workflow/workflow.proto rename sdk-core/src/main/service-protocol/service-invocation-protocol.md => service-invocation-protocol.md (88%) delete mode 100644 settings.gradle.kts diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 00a51aff..00000000 --- a/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -# -# https://help.github.com/articles/dealing-with-line-endings/ -# -# These are explicitly windows files and should use crlf -*.bat text eol=crlf - diff --git a/.github/workflows/bump.yaml b/.github/workflows/bump.yaml deleted file mode 100644 index 1e4ce481..00000000 --- a/.github/workflows/bump.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: Bump version -on: - workflow_dispatch: - inputs: - version: - description: 'Version to bump (without prepending "v")' - required: true - -jobs: - bump: - name: Bump release version - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install dasel - run: curl -sSLf "$(curl -sSLf https://api.github.com/repos/tomwright/dasel/releases/latest | grep browser_download_url | grep linux_amd64 | grep -v .gz | cut -d\" -f 4)" -L -o dasel && chmod +x dasel && mv ./dasel /usr/local/bin/dasel - - name: Bump version overwriting libs.versions.toml - run: dasel -f gradle/libs.versions.toml put -t string -v "${{ github.event.inputs.version }}" ".versions.restate" - - name: Create version bump PR - uses: peter-evans/create-pull-request@v3 - with: - title: "[Release] Bump to ${{ github.event.inputs.version }}" - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "Bump to ${{ github.event.inputs.version }}" - signoff: true - branch: "bump/${{ github.event.inputs.version }}" - body: > - This PR performs the bump of the SDK to ${{ github.event.inputs.version }}. - This PR is auto-generated by - [create-pull-request](https://github.com/peter-evans/create-pull-request). \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 02f086d1..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: CI - -on: - pull_request: - workflow_dispatch: - push: - branches: - - main - -jobs: - build-and-test: - name: Build and test (Java ${{ matrix.java }}) - runs-on: ubuntu-latest - timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - java: [ 11 ] - steps: - - uses: actions/checkout@v3 - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 - with: - java-version: ${{ matrix.java }} - distribution: 'adopt' - - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 - - - name: Build with Gradle - uses: gradle/gradle-build-action@v2 - with: - arguments: build - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: Test results (Java ${{ matrix.java }}) - path: "**/test-results/test/*.xml" - - event_file: - name: "Event File" - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Upload - uses: actions/upload-artifact@v2 - with: - name: Event File - path: ${{ github.event_path }} - - e2e: - permissions: - contents: read - issues: read - checks: write - pull-requests: write - actions: read - secrets: inherit - uses: restatedev/e2e/.github/workflows/e2e.yaml@main - with: - sdkJavaCommit: ${{ github.event.pull_request.head.sha || github.sha }} - e2eRef: main diff --git a/sdk-core/src/main/service-protocol/.github/workflows/lint.yaml b/.github/workflows/lint.yaml similarity index 100% rename from sdk-core/src/main/service-protocol/.github/workflows/lint.yaml rename to .github/workflows/lint.yaml diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml deleted file mode 100644 index 2820da9d..00000000 --- a/.github/workflows/manual-release.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Trigger publish manually - -on: - workflow_dispatch: - -jobs: - publish: - uses: ./.github/workflows/release.yml - secrets: inherit diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml deleted file mode 100644 index e1cc04ef..00000000 --- a/.github/workflows/publish-test-results.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Publish test results - -on: - workflow_run: - workflows: [ "CI" ] - types: - - completed - -jobs: - publish-test-results: - name: Publish test results - runs-on: ubuntu-latest - timeout-minutes: 10 - if: github.event.workflow_run.conclusion != 'skipped' - - steps: - - name: Download and Extract Artifacts - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - run: | - mkdir -p artifacts && cd artifacts - - artifacts_url=${{ github.event.workflow_run.artifacts_url }} - - gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact - do - IFS=$'\t' read name url <<< "$artifact" - gh api $url > "$name.zip" - unzip -d "$name" "$name.zip" - done - - - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v1 - with: - commit: ${{ github.event.workflow_run.head_sha }} - event_file: artifacts/Event File/event.json - event_name: ${{ github.event.workflow_run.event }} - files: "artifacts/**/test-results/test/*.xml" \ No newline at end of file diff --git a/.github/workflows/push-release.yml b/.github/workflows/push-release.yml deleted file mode 100644 index f56ce970..00000000 --- a/.github/workflows/push-release.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Trigger release - -on: - workflow_dispatch: - push: - branches: - - main - - -jobs: - publish-to-maven-central: - uses: ./.github/workflows/release.yml - secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 522c1d05..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Publish release - -on: - workflow_call: - -jobs: - publish: - if: github.repository == 'restatedev/sdk-java' - name: Publish - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v3 - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 - with: - java-version: 11 - distribution: 'adopt' - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 - - # Retrieve the version of the SDK - - name: Install dasel - run: curl -sSLf "$(curl -sSLf https://api.github.com/repos/tomwright/dasel/releases/latest | grep browser_download_url | grep linux_amd64 | grep -v .gz | cut -d\" -f 4)" -L -o dasel && chmod +x dasel && mv ./dasel /usr/local/bin/dasel - - name: Parse published sdk version - run: | - echo "PUBLISHED_SDK_VERSION=$(dasel -f gradle/libs.versions.toml .versions.restate)" >> "$GITHUB_ENV" - - # Dry run - - name: Publish dry-run - uses: gradle/gradle-build-action@v2 - env: - # Used for checking the signing - MAVEN_CENTRAL_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_CENTRAL_GPG_PRIVATE_KEY }} - MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.MAVEN_CENTRAL_GPG_PASSPHRASE }} - with: - arguments: publishToMavenLocal - - - name: Publish to staging area on Maven Central - uses: gradle/gradle-build-action@v2 - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - MAVEN_CENTRAL_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_CENTRAL_GPG_PRIVATE_KEY }} - MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.MAVEN_CENTRAL_GPG_PASSPHRASE }} - with: - arguments: publishToSonatype - - - name: Release staging area - uses: gradle/gradle-build-action@v2 - if: "!contains(env.PUBLISHED_SDK_VERSION, '-SNAPSHOT')" - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - MAVEN_CENTRAL_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_CENTRAL_GPG_PRIVATE_KEY }} - MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.MAVEN_CENTRAL_GPG_PASSPHRASE }} - with: - arguments: findSonatypeStagingRepository closeAndReleaseSonatypeStagingRepository diff --git a/.gitignore b/.gitignore index c93860a1..29b636a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,2 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -# Ignore Gradle project-specific cache directory -.gradle - -# Ignore Gradle build output directory -build - .idea -*.iml -.settings -.project -.classpath -.factorypath -kls_database.db +*.iml \ No newline at end of file diff --git a/.ignore b/.ignore deleted file mode 120000 index 3e4e48b0..00000000 --- a/.ignore +++ /dev/null @@ -1 +0,0 @@ -.gitignore \ No newline at end of file diff --git a/sdk-core/src/main/service-protocol/.prettierrc.toml b/.prettierrc.toml similarity index 100% rename from sdk-core/src/main/service-protocol/.prettierrc.toml rename to .prettierrc.toml diff --git a/sdk-core/src/main/service-protocol/.protolint.yaml b/.protolint.yaml similarity index 100% rename from sdk-core/src/main/service-protocol/.protolint.yaml rename to .protolint.yaml diff --git a/LICENSE b/LICENSE index 8963cdde..b81eecf5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ - MIT License +MIT License - Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +Copyright (c) 2023 - Restate Software, Inc., Restate GmbH - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE \ No newline at end of file diff --git a/README.md b/README.md index f376f093..4a0ca91f 100644 --- a/README.md +++ b/README.md @@ -1,446 +1,13 @@ -[![Documentation](https://img.shields.io/badge/doc-reference-blue)](https://docs.restate.dev) -[![javadoc](https://javadoc.io/badge2/dev.restate/sdk-api/javadoc.svg)](https://javadoc.io/doc/dev.restate) -[![Examples](https://img.shields.io/badge/view-examples-blue)](https://github.com/restatedev/examples) -[![Discord](https://img.shields.io/discord/1128210118216007792?logo=discord)](https://discord.gg/skW3AZ6uGd) -[![Twitter](https://img.shields.io/twitter/follow/restatedev.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=restatedev) +# Restate Service Protocol -# Restate JVM SDK +This repo contains specification documents and Protobuf schemas of the Restate Service Protocol. -[Restate](https://restate.dev/) is a system for easily building resilient applications using _distributed durable async/await_. This repository contains the Restate SDK for writing services using JVM languages. +* [Service invocation protocol specification](./service-invocation-protocol.md) -This SDK features: +## Development -- Implement Restate services using either: - - Java - - Kotlin coroutines -- Reuse the existing gRPC/Protobuf ecosystem to define service interfaces -- Deploy Restate services as: - - Non-blocking HTTP servers - - On AWS Lambda - -## Community - -* πŸ€—οΈ [Join our online community](https://discord.gg/skW3AZ6uGd) for help, sharing feedback and talking to the community. -* πŸ“– [Check out our documentation](https://docs.restate.dev) to get quickly started! -* πŸ“£ [Follow us on Twitter](https://twitter.com/restatedev) for staying up to date. -* πŸ™‹ [Create a GitHub issue](https://github.com/restatedev/sdk-java/issues) for requesting a new feature or reporting a problem. -* 🏠 [Visit our GitHub org](https://github.com/restatedev) for exploring other repositories. - -## Using the SDK - -### tl;dr Use project templates - -To get started, follow the [Java quickstart](https://docs.restate.dev/get_started/quickstart). - -### Setup a project (Java) - -Scaffold a project using the build tool of your choice. For example, with Gradle (Kotlin script): - -``` -gradle init --type java-application -``` - -Add the dependency [sdk-api](sdk-api): - -``` -implementation("dev.restate:sdk-api:0.6.0") -``` - -Now you need to configure the protobuf plugin to build your Protobuf contracts. For example, with Gradle (Kotlin script): - -```kts -import com.google.protobuf.gradle.id - -plugins { - // ... - id("com.google.protobuf") version "0.9.1" - // ... -} - -dependencies { - // ... - // You need the following dependencies to compile the generated code - implementation("com.google.protobuf:protobuf-java:3.24.3") - implementation("io.grpc:grpc-stub:1.58.0") - implementation("io.grpc:grpc-protobuf:1.58.0") - compileOnly("org.apache.tomcat:annotations-api:6.0.53") -} - -// Configure protoc plugin -protobuf { - protoc { artifact = "com.google.protobuf:protoc:3.24.3" } - - plugins { - // The Restate plugin depends on the gRPC generated code - id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.58.0" } - id("restate") { artifact = "dev.restate:protoc-gen-restate:0.6.0:all@jar" } - } - - generateProtoTasks { - all().forEach { - it.plugins { - id("grpc") - id("restate") - } - } - } -} -``` - -For Maven you can use the [xolstice Protobuf plugin](https://www.xolstice.org/protobuf-maven-plugin/index.html). - -### Setup a project (Kotlin) - -Scaffold a project using the build tool of your choice. For example, with Gradle (Kotlin script): - -``` -gradle init --type kotlin-application -``` - -Add the dependency [`sdk-api-kotlin`](sdk-api-kotlin): - -``` -implementation("dev.restate:sdk-api-kotlin:0.6.0") -``` - -Now you need to configure the protobuf plugin to build your Protobuf contracts. For example, with Gradle (Kotlin script): - -```kts -import com.google.protobuf.gradle.id - -plugins { - // ... - id("com.google.protobuf") version "0.9.1" - // ... -} - -dependencies { - // ... - // You need the following dependencies to compile the generated code - implementation("com.google.protobuf:protobuf-java:3.24.3") - implementation("com.google.protobuf:protobuf-kotlin:3.24.3") - implementation("io.grpc:grpc-stub:1.58.0") - implementation("io.grpc:grpc-protobuf:1.58.0") - implementation("io.grpc:grpc-kotlin-stub:1.4.0") { exclude("javax.annotation", "javax.annotation-api") } - compileOnly("org.apache.tomcat:annotations-api:6.0.53") -} - -// Configure protoc plugin -protobuf { - protoc { artifact = "com.google.protobuf:protoc:3.24.3" } - - plugins { - // The gRPC Kotlin plugin depends on the gRPC generated code - id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.58.0" } - id("restate") { artifact = "dev.restate:protoc-gen-restate:0.6.0:all@jar" } - } - - generateProtoTasks { - all().forEach { - it.plugins { - id("grpc") - id("restate") { - option("kotlin") - } - } - it.builtins { - // The Kotlin codegen depends on the Java generated code - java {} - id("kotlin") - } - } - } -} -``` - -### Add the Protobuf contract - -Now you can add the Protobuf contract under `src/main/proto`. For example: - -```protobuf -syntax = "proto3"; -package greeter; - -option java_package = "my.greeter"; -option java_outer_classname = "GreeterProto"; - -import "dev/restate/ext.proto"; - -service Greeter { - option (dev.restate.ext.service_type) = KEYED; - - rpc Greet (GreetRequest) returns (GreetResponse); -} - -message GreetRequest { - string name = 1 [(dev.restate.ext.field) = KEY]; -} - -message GreetResponse { - string message = 1; -} -``` - -By using the Gradle or Maven plugin, the code is automatically re-generated on every build. - -### Implement the service (Java) - -Implement the service in a new class, for example: - -```java -public class Greeter extends GreeterRestate.GreeterRestateImplBase { - - private static final StateKey COUNT = StateKey.of("total", CoreSerdes.LONG); - - @Override - public GreetResponse greet(RestateContext ctx, GreetRequest request) { - long count = ctx.get(COUNT).orElse(0L); - ctx.set(COUNT, count + 1); - - return GreetResponse.newBuilder() - .setMessage(String.format("Hello %s for the %d time!", request.getName(), count)) - .build(); - } -} -``` - -If you want to use POJOs for state, check [how to use Jackson](#state-serde-using-jackson). - -### Implement the service (Kotlin) - -Implement the service in a new class, for example: - -```kotlin -class Greeter : GreeterRestateKtImplBase() { - companion object { - private val COUNT = StateKey.of("total", CoreSerdes.LONG) - } - - override suspend fun greet(context: RestateContext, request: GreetRequest): GreetResponse { - val count = context.get(COUNT) ?: 0L - context.set(COUNT, count + 1) - return greetResponse { message = "Hello ${request.name} for the $count time!" } - } -} -``` - -If you want to use POJOs for state, check [how to use Jackson](#state-serde-using-jackson). - -### Deploy the service (HTTP Server) - -To deploy the Restate service as HTTP server, add [`sdk-http-vertx`](sdk-http-vertx) to the dependencies. For example, in Gradle: - -``` -implementation("dev.restate:sdk-http-vertx:0.6.0") -``` - -To deploy the service, add the following code to the `main`. For example in Java: - -```java -public static void main(String[] args) { - RestateHttpEndpointBuilder.builder() - .withService(new Greeter()) - .buildAndListen(); -} -``` - -In Kotlin: - -```kotlin -fun main() { - RestateHttpEndpointBuilder.builder() - .withService(Greeter()) - .buildAndListen() -} -``` - -Execute the project. For example, using Gradle: - -``` -gradle run -``` - -### Deploy the service (AWS Lambda) - -To deploy the Restate service as Lambda, add [`sdk-lambda`](sdk-lambda) to the dependencies. For example, in Gradle: - -``` -implementation("dev.restate:sdk-lambda:0.6.0") -``` - -Configure the build tool to generate Fat-JARs, which are required by AWS Lambda to correctly load the JAR. For example, using Gradle: - -``` -plugins { - // ... - // The shadow plugin generates a shadow JAR ready for AWS Lambda - id("com.github.johnrengelman.shadow").version("7.1.2") - // ... -} -``` - -Now create the Lambda handler invoking the service. For example, in Java: - -```java -public class MyLambdaHandler extends BaseRestateLambdaHandler { - @Override - public void register(RestateLambdaEndpointBuilder builder) { - builder.withService(new Greeter()); - } -} -``` - -In Kotlin: - -```kotlin -class MyLambdaHandler : BaseRestateLambdaHandler { - override fun register(builder: RestateLambdaEndpointBuilder) { - builder.withService(Greeter()) - } -} -``` - -Now build the Fat-JAR. For example, using Gradle: - -``` -gradle shadowJar -``` - -You can now upload the generated Jar in AWS Lambda, and configure `MyLambdaHandler` as the Lambda class in the AWS UI. - -### Additional setup - -#### State ser/de using Jackson - -State ser/de is defined by the interface `Serde`. If you want to use [Jackson Databind](https://github.com/FasterXML/jackson) to ser/de POJOs to JSON, add the dependency [`sdk-serde-jackson`](sdk-serde-jackson). - -For example, in Gradle: - -``` -implementation("dev.restate:sdk-serde-jackson:0.6.0") -``` - -And then use `JacksonSerdes`: - -```java -private static final StateKey PERSON = StateKey.of("person", JacksonSerdes.of(Person.class)); -``` - -#### Logging - -The SDK uses log4j2 as logging facade. To enable logging, add the `log4j2` implementation to the dependencies: - -``` -implementation("org.apache.logging.log4j:log4j-core:2.20.0") -``` - -And configure the logging adding the file `resources/log4j2.properties`: - -``` -# Set to debug or trace if log4j initialization is failing -status = warn - -# Console appender configuration -appender.console.type = Console -appender.console.name = consoleLogger -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %notEmpty{[%X{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 -``` - -The SDK injects the following additional metadata to the logging context that can be used for filtering as well: - -* `restateInvocationTarget`: invocation target, e.g. `counter.Counter/Add`. -* `restateInvocationId`: Invocation identifier, to be used in Restate observability tools. See https://docs.restate.dev/operate/invocation#invocation-identifier. -* `restateInvocationStatus`: Invocation status, can be `WAITING_START`, `REPLAYING`, `PROCESSING`, `CLOSED`. - -When assembling fat-jars, make sure to enable merging META-INF/services files. For more info, see https://github.com/apache/logging-log4j2/issues/2099. - -#### Tracing with OpenTelemetry - -The SDK can generate additional tracing information on top of what Restate already publishes. See https://docs.restate.dev/operate/monitoring/tracing to configure Restate tracing. - -You can the additional SDK tracing information by configuring the `OpenTelemetry` in the `RestateHttpEndpointBuilder`/`LambdaRestateServer`. - -For example, to set up tracing using environment variables, add the following modules to your dependencies: - -``` -implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.31.0") -implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.31.0") -``` - -And then configure it in the Restate builder: - -```java -.withOpenTelemetry(AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk()) -``` - -By exporting the following environment variables the OpenTelemetry SDK will be automatically configured to push traces: - -```shell -export OTEL_SERVICE_NAME=my-service -export OTEL_TRACES_SAMPLER=always_on -export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:14250 -``` - -Please refer to the [Opentelemetry manual instrumentation documentation](https://opentelemetry.io/docs/instrumentation/java/manual/#manual-instrumentation-setup) and the [autoconfigure documentation](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md) for more info. - -## Contributing - -We’re excited if you join the Restate community and start contributing! -Whether it is feature requests, bug reports, ideas & feedback or PRs, we appreciate any and all contributions. -We know that your time is precious and, therefore, deeply value any effort to contribute! - -### Building the SDK locally - -Prerequisites: - -- JDK >= 11 -- Docker or Podman - -To build the SDK: - -```shell -./gradlew build -``` - -To run the tests: - -```shell -./gradlew check -``` - -To publish local snapshots of the project: - -```shell -./gradlew -DskipSigning publishToMavenLocal -``` - -To update the [`proto`](https://github.com/restatedev/proto/) git subtree: - -```shell -git subtree pull --prefix sdk-common/src/main/proto/ git@github.com:restatedev/proto.git main --squash -``` - -To update the [`service-protocol`](https://github.com/restatedev/service-protocol/) git subtree: +To format the spec document: ```shell -git subtree pull --prefix sdk-core/src/main/service-protocol/ git@github.com:restatedev/service-protocol.git main --squash -``` +npx prettier -w service-invocation-protocol.md +``` \ No newline at end of file diff --git a/admin-client/build.gradle.kts b/admin-client/build.gradle.kts deleted file mode 100644 index 1718e6aa..00000000 --- a/admin-client/build.gradle.kts +++ /dev/null @@ -1,56 +0,0 @@ -import net.ltgt.gradle.errorprone.errorprone -import org.openapitools.generator.gradle.plugin.tasks.GenerateTask - -plugins { - `java-library` - id("org.openapi.generator") version "6.6.0" - `library-publishing-conventions` -} - -description = "Code-generated Admin API client for Restate" - -dependencies { - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.core) - implementation(jacksonLibs.jackson.databind) - implementation(jacksonLibs.jackson.jsr310) - implementation("org.openapitools:jackson-databind-nullable:0.2.6") - - // Required for the annotations - compileOnly("org.apache.tomcat:annotations-api:6.0.53") - compileOnly("com.google.code.findbugs:jsr305:3.0.2") -} - -// Add generated output to source sets -sourceSets { main { java.srcDir(tasks.named("openApiGenerate")) } } - -// Configure openapi generator -tasks.withType { - inputSpec.set("$projectDir/src/main/openapi/meta.json") - - // Java 9+ HTTP Client using Jackson - generatorName.set("java") - library.set("native") - - // Package names - invokerPackage.set("dev.restate.admin.client") - apiPackage.set("dev.restate.admin.api") - modelPackage.set("dev.restate.admin.model") - - // We don't need these - generateApiTests.set(false) - generateApiDocumentation.set(false) - generateModelTests.set(false) - generateModelDocumentation.set(false) - - finalizedBy("spotlessJava") -} - -tasks.withType().configureEach { - // Disable errorprone for this module - options.errorprone.disableAllChecks.set(true) -} - -configure { - java { targetExclude(fileTree("build/generate-resources") { include("**/*.java") }) } -} diff --git a/admin-client/src/main/openapi/meta.json b/admin-client/src/main/openapi/meta.json deleted file mode 100644 index 8607afcf..00000000 --- a/admin-client/src/main/openapi/meta.json +++ /dev/null @@ -1 +0,0 @@ -{"openapi":"3.0.0","info":{"title":"Admin API","version":"0.9.0"},"paths":{"/invocations/{invocation_id}":{"delete":{"tags":["invocation"],"summary":"Terminate an invocation","description":"Terminate the given invocation. By default, an invocation is terminated by gracefully cancelling it. This ensures virtual object state consistency. Alternatively, an invocation can be killed which does not guarantee consistency for virtual object instance state, in-flight invocations to other services, etc.","operationId":"terminate_invocation","parameters":[{"name":"invocation_id","in":"path","description":"Invocation identifier.","required":true,"schema":{"type":"string"}},{"name":"mode","in":"query","description":"If cancel, it will gracefully terminate the invocation. If kill, it will terminate the invocation with a hard stop.","style":"simple","schema":{"$ref":"#/components/schemas/TerminationMode"}}],"responses":{"202":{"description":"Accepted"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/deployments":{"get":{"tags":["deployment"],"summary":"List deployments","description":"List all registered deployments.","operationId":"list_deployments","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDeploymentsResponse"}}}}}},"post":{"tags":["deployment"],"summary":"Create deployment","description":"Create deployment. Restate will invoke the endpoint to gather additional information required for registration, such as the services exposed by the deployment. If the deployment is already registered, this method will fail unless `force` is set to `true`.","operationId":"create_deployment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeploymentRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeploymentResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"Health check","description":"Check REST API Health.","operationId":"health","responses":{"200":{"description":"OK"}}}},"/services/{service}/handlers/{handler}":{"get":{"tags":["service_handler"],"summary":"Get service handler","description":"Get the handler of a service","operationId":"get_service_handler","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}},{"name":"handler","in":"path","description":"Handler name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandlerMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/deployments/{deployment}":{"get":{"tags":["deployment"],"summary":"Get deployment","description":"Get deployment metadata","operationId":"get_deployment","parameters":[{"name":"deployment","in":"path","description":"Deployment identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailedDeploymentResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}},"delete":{"tags":["deployment"],"summary":"Delete deployment","description":"Delete deployment. Currently it's supported to remove a deployment only using the force flag","operationId":"delete_deployment","parameters":[{"name":"deployment","in":"path","description":"Deployment identifier","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","description":"If true, the deployment will be forcefully deleted. This might break in-flight invocations, use with caution.","style":"simple","schema":{"type":"boolean"}}],"responses":{"202":{"description":"Accepted"},"501":{"description":"Not implemented. Only using the force flag is supported at the moment."},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/subscriptions":{"get":{"tags":["subscription"],"summary":"List subscriptions","description":"List all subscriptions.","operationId":"list_subscriptions","parameters":[{"name":"sink","in":"query","description":"Filter by the exact specified sink.","style":"simple","schema":{"type":"string"}},{"name":"source","in":"query","description":"Filter by the exact specified source.","style":"simple","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSubscriptionsResponse"}}}}}},"post":{"tags":["subscription"],"summary":"Create subscription","description":"Create subscription.","operationId":"create_subscription","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubscriptionRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/openapi":{"get":{"tags":["openapi"],"summary":"OpenAPI specification","externalDocs":{"url":"https://swagger.io/specification/"},"operationId":"openapi_spec","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}},"/services/{service}/handlers":{"get":{"tags":["service_handler"],"summary":"List service handlers","description":"List all the handlers of the given service.","operationId":"list_service_handlers","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServiceHandlersResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}":{"get":{"tags":["service"],"summary":"Get service","description":"Get a registered service.","operationId":"get_service","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}},"patch":{"tags":["service"],"summary":"Modify a service","description":"Modify a registered service.","operationId":"modify_service","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifyServiceRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services/{service}/state":{"post":{"tags":["service"],"summary":"Modify a service state","description":"Modify service state","operationId":"modify_service_state","parameters":[{"name":"service","in":"path","description":"Fully qualified service name.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifyServiceStateRequest"}}},"required":true},"responses":{"202":{"description":"Accepted"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/subscriptions/{subscription}":{"get":{"tags":["subscription"],"summary":"Get subscription","description":"Get subscription","operationId":"get_subscription","parameters":[{"name":"subscription","in":"path","description":"Subscription identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}},"delete":{"tags":["subscription"],"summary":"Delete subscription","description":"Delete subscription.","operationId":"delete_subscription","parameters":[{"name":"subscription","in":"path","description":"Subscription identifier","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted"},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}},"/services":{"get":{"tags":["service"],"summary":"List services","description":"List all registered services.","operationId":"list_services","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListServicesResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}},"503":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDescriptionResponse"}}}}}}}},"components":{"schemas":{"ErrorDescriptionResponse":{"title":"Error description response","description":"Error details of the response","type":"object","required":["message"],"properties":{"message":{"type":"string"},"restate_code":{"title":"Restate code","description":"Restate error code describing this error","type":"string","nullable":true}}},"TerminationMode":{"type":"string","enum":["Cancel","Kill"]},"RegisterDeploymentRequest":{"anyOf":[{"type":"object","required":["uri"],"properties":{"uri":{"title":"Uri","description":"Uri to use to discover/invoke the http deployment.","type":"string"},"additional_headers":{"title":"Additional headers","description":"Additional headers added to the discover/invoke requests to the deployment.","type":"object","additionalProperties":{"type":"string"},"nullable":true},"force":{"title":"Force","description":"If `true`, it will override, if existing, any deployment using the same `uri`. Beware that this can lead in-flight invocations to an unrecoverable error state.\n\nBy default, this is `true` but it might change in future to `false`.\n\nSee the [versioning documentation](https://docs.restate.dev/services/upgrades-removal) for more information.","default":true,"type":"boolean"},"dry_run":{"title":"Dry-run mode","description":"If `true`, discovery will run but the deployment will not be registered. This is useful to see the impact of a new deployment before registering it.","default":false,"type":"boolean"}}},{"type":"object","required":["arn"],"properties":{"arn":{"title":"ARN","description":"ARN to use to discover/invoke the lambda deployment.","type":"string"},"assume_role_arn":{"title":"Assume role ARN","description":"Optional ARN of a role to assume when invoking the addressed Lambda, to support role chaining","type":"string","nullable":true},"additional_headers":{"title":"Additional headers","description":"Additional headers added to the discover/invoke requests to the deployment.","type":"object","additionalProperties":{"type":"string"},"nullable":true},"force":{"title":"Force","description":"If `true`, it will override, if existing, any deployment using the same `uri`. Beware that this can lead in-flight invocations to an unrecoverable error state.\n\nBy default, this is `true` but it might change in future to `false`.\n\nSee the [versioning documentation](https://docs.restate.dev/services/upgrades-removal) for more information.","default":true,"type":"boolean"},"dry_run":{"title":"Dry-run mode","description":"If `true`, discovery will run but the deployment will not be registered. This is useful to see the impact of a new deployment before registering it.","default":false,"type":"boolean"}}}]},"RegisterDeploymentResponse":{"type":"object","required":["id","services"],"properties":{"id":{"$ref":"#/components/schemas/String"},"services":{"type":"array","items":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"String":{"type":"string"},"ServiceMetadata":{"type":"object","required":["deployment_id","handlers","idempotency_retention","name","public","revision","ty"],"properties":{"name":{"title":"Name","description":"Fully qualified name of the service","type":"string"},"handlers":{"type":"array","items":{"$ref":"#/components/schemas/HandlerMetadata"}},"ty":{"$ref":"#/components/schemas/ServiceType"},"deployment_id":{"title":"Deployment Id","description":"Deployment exposing the latest revision of the service.","type":"string"},"revision":{"title":"Revision","description":"Latest revision of the service.","type":"integer","format":"uint32","minimum":0.0},"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","type":"boolean"},"idempotency_retention":{"title":"Idempotency retention","description":"The retention duration of idempotent requests for this service.","type":"string"}}},"HandlerMetadata":{"type":"object","required":["input_description","name","output_description","ty"],"properties":{"name":{"type":"string"},"ty":{"$ref":"#/components/schemas/HandlerType"},"input_description":{"type":"string"},"output_description":{"type":"string"}}},"HandlerType":{"type":"string","enum":["Exclusive","Shared"]},"ServiceType":{"type":"string","enum":["Service","VirtualObject"]},"DetailedDeploymentResponse":{"type":"object","anyOf":[{"type":"object","required":["created_at","protocol_type","uri"],"properties":{"uri":{"type":"string"},"protocol_type":{"$ref":"#/components/schemas/ProtocolType"},"additional_headers":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"}}},{"type":"object","required":["arn","created_at"],"properties":{"arn":{"$ref":"#/components/schemas/LambdaARN"},"assume_role_arn":{"type":"string","nullable":true},"additional_headers":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"}}}],"required":["id","services"],"properties":{"id":{"$ref":"#/components/schemas/String"},"services":{"title":"Services","description":"List of services exposed by this deployment.","type":"array","items":{"$ref":"#/components/schemas/ServiceMetadata"}}}},"ProtocolType":{"type":"string","enum":["RequestResponse","BidiStream"]},"LambdaARN":{"type":"string","format":"arn"},"CreateSubscriptionRequest":{"type":"object","required":["sink","source"],"properties":{"source":{"title":"Source","description":"Source uri. Accepted forms:\n\n* `kafka:///`, e.g. `kafka://my-cluster/my-topic`","type":"string"},"sink":{"title":"Sink","description":"Sink uri. Accepted forms:\n\n* `service:///`, e.g. `service://Counter/count`","type":"string"},"options":{"title":"Options","description":"Additional options to apply to the subscription.","type":"object","additionalProperties":{"type":"string"},"nullable":true}}},"SubscriptionResponse":{"type":"object","required":["id","options","sink","source"],"properties":{"id":{"$ref":"#/components/schemas/String"},"source":{"type":"string"},"sink":{"type":"string"},"options":{"type":"object","additionalProperties":{"type":"string"}}}},"ListServiceHandlersResponse":{"type":"object","required":["handlers"],"properties":{"handlers":{"type":"array","items":{"$ref":"#/components/schemas/HandlerMetadata"}}}},"ModifyServiceRequest":{"type":"object","properties":{"public":{"title":"Public","description":"If true, the service can be invoked through the ingress. If false, the service can be invoked only from another Restate service.","default":null,"type":"boolean","nullable":true},"idempotency_retention":{"title":"Idempotency retention","description":"Modify the retention of idempotent requests for this service.","default":null,"type":"string","nullable":true}}},"ListDeploymentsResponse":{"type":"object","required":["deployments"],"properties":{"deployments":{"type":"array","items":{"$ref":"#/components/schemas/DeploymentResponse"}}}},"DeploymentResponse":{"type":"object","anyOf":[{"type":"object","required":["created_at","protocol_type","uri"],"properties":{"uri":{"type":"string"},"protocol_type":{"$ref":"#/components/schemas/ProtocolType"},"additional_headers":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"}}},{"type":"object","required":["arn","created_at"],"properties":{"arn":{"$ref":"#/components/schemas/LambdaARN"},"assume_role_arn":{"type":"string","nullable":true},"additional_headers":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"}}}],"required":["id","services"],"properties":{"id":{"$ref":"#/components/schemas/String"},"services":{"title":"Services","description":"List of services exposed by this deployment.","type":"array","items":{"$ref":"#/components/schemas/ServiceNameRevPair"}}}},"ServiceNameRevPair":{"type":"object","required":["name","revision"],"properties":{"name":{"type":"string"},"revision":{"type":"integer","format":"uint32","minimum":0.0}}},"ListSubscriptionsResponse":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"ModifyServiceStateRequest":{"type":"object","required":["new_state","object_key"],"properties":{"version":{"title":"Version","description":"If set, the latest version of the state is compared with this value and the operation will fail when the versions differ.","type":"string","nullable":true},"object_key":{"title":"Service key","description":"To what virtual object key to apply this change","type":"string"},"new_state":{"title":"New State","description":"The new state to replace the previous state with","type":"object","additionalProperties":{"type":"array","items":{"type":"integer","format":"uint8","minimum":0.0}}}}},"ListServicesResponse":{"type":"object","required":["services"],"properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/ServiceMetadata"}}}}}}} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 4f7110e2..00000000 --- a/build.gradle.kts +++ /dev/null @@ -1,124 +0,0 @@ -import net.ltgt.gradle.errorprone.errorprone - -plugins { - java - kotlin("jvm") version "1.9.22" apply false - kotlin("plugin.serialization") version "1.9.22" apply false - - id("net.ltgt.errorprone") version "3.0.1" - id("com.github.jk1.dependency-license-report") version "2.0" - id("io.github.gradle-nexus.publish-plugin") version "1.3.0" - - alias(pluginLibs.plugins.spotless) -} - -val protobufVersion = coreLibs.versions.protobuf.get() -val restateVersion = libs.versions.restate.get() - -allprojects { - apply(plugin = "com.diffplug.spotless") - apply(plugin = "com.github.jk1.dependency-license-report") - - group = "dev.restate" - version = restateVersion - - configure { - java { - targetExclude("build/**/*.java") - - googleJavaFormat() - - licenseHeaderFile("$rootDir/config/license-header") - } - - format("proto") { - target("**/*.proto") - - // Exclude proto and service-protocol directories because those get the license header from - // their repos. - targetExclude( - fileTree("$rootDir/sdk-common/src/main/proto") { include("**/*.*") }, - fileTree("$rootDir/sdk-core/src/main/service-protocol") { include("**/*.*") }) - - licenseHeaderFile("$rootDir/config/license-header", "syntax") - } - - kotlin { - targetExclude("build/generated/**/*.kt") - ktfmt() - licenseHeaderFile("$rootDir/config/license-header") - } - - kotlinGradle { ktfmt() } - - format("properties") { - target("**/*.properties") - trimTrailingWhitespace() - } - } - - tasks { check { dependsOn(checkLicense) } } - - licenseReport { - renderers = arrayOf(com.github.jk1.license.render.CsvReportRenderer()) - - excludeBoms = true - - excludes = - arrayOf( - "io.vertx:vertx-stack-depchain", // Vertx bom file - "com.google.guava:guava-parent", // Guava bom - // kotlinx dependencies are APL 2, but somehow the plugin doesn't recognize that. - "org.jetbrains.kotlinx:kotlinx-coroutines-core", - "org.jetbrains.kotlinx:kotlinx-serialization-core", - "org.jetbrains.kotlinx:kotlinx-serialization-json", - ) - - allowedLicensesFile = file("$rootDir/config/allowed-licenses.json") - filters = - arrayOf( - com.github.jk1.license.filter.LicenseBundleNormalizer( - "$rootDir/config/license-normalizer-bundle.json", true)) - } -} - -subprojects { - apply(plugin = "java") - apply(plugin = "net.ltgt.errorprone") - - dependencies { errorprone("com.google.errorprone:error_prone_core:2.13.1") } - - // Configure the java toolchain to use. If not found, it will be downloaded automatically - java { - toolchain { languageVersion = JavaLanguageVersion.of(11) } - - withJavadocJar() - withSourcesJar() - } - - tasks.withType().configureEach { - options.errorprone.disableWarningsInGeneratedCode.set(true) - options.errorprone.disable( - // We use toString() in proto messages for debugging reasons. - "LiteProtoToString", - // This check is proposing to use a guava API instead... - "StringSplitter", - // This is conflicting with a javadoc warn lint - "MissingSummary") - options.errorprone.excludedPaths.set(".*/build/generated/.*") - } - - tasks.withType { useJUnitPlatform() } -} - -nexusPublishing { - repositories { - sonatype { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - - username.set(System.getenv("MAVEN_CENTRAL_USERNAME") ?: return@sonatype) - password.set(System.getenv("MAVEN_CENTRAL_TOKEN") ?: return@sonatype) - } - } -} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 876c922b..00000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -plugins { - `kotlin-dsl` -} - -repositories { - mavenCentral() -} diff --git a/buildSrc/src/main/kotlin/library-publishing-conventions.gradle.kts b/buildSrc/src/main/kotlin/library-publishing-conventions.gradle.kts deleted file mode 100644 index e5701047..00000000 --- a/buildSrc/src/main/kotlin/library-publishing-conventions.gradle.kts +++ /dev/null @@ -1,56 +0,0 @@ -plugins { - `maven-publish` - signing -} - -project.afterEvaluate { - publishing { - publications { - create("maven") { - groupId = "dev.restate" - artifactId = project.name - - from(components["java"]) - - pom { - name = "Restate SDK :: ${project.name}" - description = project.description!! - url = "https://github.com/restatedev/sdk-java" - inceptionYear = "2023" - - licenses { - license { - name = "MIT License" - url = "https://opensource.org/license/mit/" - } - } - - scm { - connection = "scm:git:git://github.com/restatedev/sdk-java.git" - developerConnection = "scm:git:ssh://github.com/restatedev/sdk-java.git" - url = "https://github.com/restatedev/sdk-java" - } - - developers { - developer { - name = "Francesco Guardiani" - id = "slinkydeveloper" - email = "francescoguard@gmail.com" - } - } - } - } - } - } - - signing { - setRequired { !project.hasProperty("skipSigning") } - - val key = System.getenv("MAVEN_CENTRAL_GPG_PRIVATE_KEY") ?: return@signing - val password = System.getenv("MAVEN_CENTRAL_GPG_PASSPHRASE") ?: return@signing - val publishing: PublishingExtension by project - - useInMemoryPgpKeys(key, password) - sign(publishing.publications["maven"]) - } -} \ No newline at end of file diff --git a/config/allowed-licenses.json b/config/allowed-licenses.json deleted file mode 100644 index 7ef70493..00000000 --- a/config/allowed-licenses.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "allowedLicenses": [ - { - "moduleLicense": "Apache License, Version 2.0" - }, - { - "moduleLicense": "The MIT License (MIT)" - }, - { - "moduleLicense": "The 3-Clause BSD License" - }, - { - "moduleLicense": "The 2-Clause BSD License" - }, - { - "moduleLicense": "Eclipse Public License - v 2.0" - }, - { - "moduleLicense": "Eclipse Public License - v 1.0" - }, - { - "moduleName": "org.jetbrains.kotlin:kotlin-stdlib-common" - } - ] -} \ No newline at end of file diff --git a/config/license-header b/config/license-header deleted file mode 100644 index 0e5fe0c8..00000000 --- a/config/license-header +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE \ No newline at end of file diff --git a/config/license-normalizer-bundle.json b/config/license-normalizer-bundle.json deleted file mode 100644 index cc8ee5f4..00000000 --- a/config/license-normalizer-bundle.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "bundles": [ - { - "bundleName": "Apache-2.0", - "licenseName": "Apache License, Version 2.0", - "licenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" - }, - { - "bundleName": "MIT", - "licenseName": "The MIT License (MIT)" - } - ], - "transformationRules": [ - { - "bundleName": "Apache-2.0", - "licenseNamePattern": ".*The Apache Software License, Version 2\\.0.*" - }, - { - "bundleName": "Apache-2.0", - "licenseNamePattern": "Apache[ |-|_]2.*" - }, - { - "bundleName": "Apache-2.0", - "licenseNamePattern": "ASL 2\\.0" - }, - { - "bundleName": "Apache-2.0", - "licenseNamePattern": ".*Apache License,?( Version)? 2.*" - }, - { - "bundleName": "MIT", - "licenseNamePattern": ".*MIT License.*" - } - ] -} \ No newline at end of file diff --git a/dev/restate/service/discovery.proto b/dev/restate/service/discovery.proto new file mode 100644 index 00000000..e385c920 --- /dev/null +++ b/dev/restate/service/discovery.proto @@ -0,0 +1,22 @@ +// Copyright (c) 2024 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate service protocol, 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/service-protocol/blob/main/LICENSE + +syntax = "proto3"; + +package dev.restate.service.discovery; + +option java_package = "dev.restate.generated.service.discovery"; +option go_package = "restate.dev/sdk-go/pb/service/discovery"; + +// Service discovery protocol version. +enum ServiceDiscoveryProtocolVersion { + SERVICE_DISCOVERY_PROTOCOL_VERSION_UNSPECIFIED = 0; + // initial service discovery protocol version using endpoint_manifest_schema.json + V1 = 1; +} diff --git a/sdk-core/src/main/service-protocol/dev/restate/service/protocol.proto b/dev/restate/service/protocol.proto similarity index 88% rename from sdk-core/src/main/service-protocol/dev/restate/service/protocol.proto rename to dev/restate/service/protocol.proto index 3f31ebf7..333a36e8 100644 --- a/sdk-core/src/main/service-protocol/dev/restate/service/protocol.proto +++ b/dev/restate/service/protocol.proto @@ -14,6 +14,13 @@ package dev.restate.service.protocol; option java_package = "dev.restate.generated.service.protocol"; option go_package = "restate.dev/sdk-go/pb/service/protocol"; +// Service protocol version. +enum ServiceProtocolVersion { + SERVICE_PROTOCOL_VERSION_UNSPECIFIED = 0; + // initial service protocol version + V1 = 1; +} + // --- Core frames --- // Type: 0x0000 + 0 @@ -200,6 +207,60 @@ message GetStateKeysEntryMessage { string name = 12; } +// Completable: Yes +// Fallible: No +// Type: 0x0800 + 8 +message GetPromiseEntryMessage { + string key = 1; + + oneof result { + bytes value = 14; + Failure failure = 15; + }; + + // Entry name + string name = 12; +} + +// Completable: Yes +// Fallible: No +// Type: 0x0800 + 9 +message PeekPromiseEntryMessage { + string key = 1; + + oneof result { + Empty empty = 13; + bytes value = 14; + Failure failure = 15; + }; + + // Entry name + string name = 12; +} + +// Completable: Yes +// Fallible: No +// Type: 0x0800 + A +message CompletePromiseEntryMessage { + string key = 1; + + // The value to use to complete the promise + oneof completion { + bytes completion_value = 2; + Failure completion_failure = 3; + }; + + oneof result { + // Returns empty if value was set successfully + Empty empty = 13; + // Returns a failure if the promise was already completed + Failure failure = 15; + } + + // Entry name + string name = 12; +} + // ------ Syscalls ------ // Completable: Yes @@ -327,4 +388,4 @@ message Header { } message Empty { -} \ No newline at end of file +} diff --git a/development/README.md b/development/README.md deleted file mode 100644 index 9e51af7d..00000000 --- a/development/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Developer documentation - -This directory contains documentation relevant for Restate developers. - -## Table of contents - -[developer-guidelines.md](developer-guidelines.md) contains guidelines for the development in this repository. \ No newline at end of file diff --git a/development/developer-guidelines.md b/development/developer-guidelines.md deleted file mode 100644 index d7ff47c3..00000000 --- a/development/developer-guidelines.md +++ /dev/null @@ -1,5 +0,0 @@ -# Developer guidelines - -The same guidelines as the ones -outlined [here](https://github.com/restatedev/runtime/blob/main/doc/dev/development-guidelines.md) apply to the -development in this repository. \ No newline at end of file diff --git a/development/release.md b/development/release.md deleted file mode 100644 index 5dd4166a..00000000 --- a/development/release.md +++ /dev/null @@ -1,11 +0,0 @@ -# Releasing sdk-java - -To release sdk-java: - -* First make sure `service-protocol` and `proto` subtrees are updated to the latest version. See the main [README.md](../README.md#contributing-to-the-sdk) -* Change the version to the desired version using the [Bump version workflow](https://github.com/restatedev/sdk-java/actions/workflows/bump.yaml). -* Merge the auto generated PR -* Wait for CI on main to execute the release -* Create the Github Release manually -* Change the version again to the next `-SNAPSHOT` version (e.g. from `0.4.0` to `0.5.0-SNAPSHOT`, from `0.4.1` to `0.5.0-SNAPSHOT`) -* Merge the auto generated PR diff --git a/sdk-core/src/main/service-protocol/deployment_manifest_schema.json b/endpoint_manifest_schema.json similarity index 89% rename from sdk-core/src/main/service-protocol/deployment_manifest_schema.json rename to endpoint_manifest_schema.json index 8523e407..4ef72846 100644 --- a/sdk-core/src/main/service-protocol/deployment_manifest_schema.json +++ b/endpoint_manifest_schema.json @@ -1,9 +1,9 @@ { - "$id": "https://restate.dev/deployment.manifest.json", + "$id": "https://restate.dev/endpoint.manifest.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", - "title": "Deployment", - "description": "Restate deployment manifest", + "title": "Endpoint", + "description": "Restate endpoint manifest v1", "properties": { "protocolMode": { "title": "ProtocolMode", @@ -11,11 +11,15 @@ }, "minProtocolVersion": { "type": "integer", - "minimum": 0 + "minimum": 1, + "maximum": 2147483647, + "description": "Minimum supported protocol version" }, "maxProtocolVersion": { "type": "integer", - "maximum": 0 + "minimum": 1, + "maximum": 2147483647, + "description": "Maximum supported protocol version" }, "services": { "type": "array", @@ -29,7 +33,7 @@ }, "ty": { "title": "ServiceType", - "enum": ["VIRTUAL_OBJECT", "SERVICE"] + "enum": ["VIRTUAL_OBJECT", "SERVICE", "WORKFLOW"] }, "handlers": { "type": "array", @@ -43,8 +47,8 @@ }, "ty": { "title": "HandlerType", - "enum": ["EXCLUSIVE", "SHARED"], - "description": "If unspecified, defaults to EXCLUSIVE for Virtual Object. This should be unset for Services." + "enum": ["WORKFLOW", "EXCLUSIVE", "SHARED"], + "description": "If unspecified, defaults to EXCLUSIVE for Virtual Object or WORKFLOW for Workflows. This should be unset for Services." }, "input": { "type": "object", diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 2cfb6ee5..00000000 --- a/examples/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Examples - -This directory contains different examples of the SDK features. - -For a sample project configuration and more elaborated examples, check out the [examples repo](https://github.com/restatedev/examples). - -Available examples: - -* [`Counter`](src/main/java/my/restate/sdk/examples/Counter.java): Shows a simple virtual object using state primitives. -* [`CounterKt`](src/main/kotlin/my/restate/sdk/examples/CounterKt.kt): Same as `Counter` but using Kotlin. -* [`LoanWorkflow`](src/main/java/my/restate/sdk/examples/LoanWorkflow.java): Shows a simple workflow example using the Workflow API. - -## Package the examples for Lambda - -Run: - -```shell -./gradlew shadowJar -``` - -You'll find the shadowed jar in the `build` directory. - -The class to configure in Lambda is `my.restate.sdk.examples.LambdaHandler`. - -By default, the [`my.restate.sdk.examples.Counter`](src/main/java/my/restate/sdk/examples/Counter.java) virtual object is deployed. Set the env variable `LAMBDA_FACTORY_SERVICE_CLASS` to one of the available example classes to change the deployed class. - -## Running the examples (HTTP) - -You can run the Java Counter example via: - -```shell -./gradlew :examples:run -``` - -You can modify the class to run setting `-PmainClass=`, for example, in order to run the Kotlin implementation: - -```shell -./gradlew :examples:run -PmainClass=my.restate.sdk.examples.CounterKtKt -``` - -## Invoking the Counter - -If you want to invoke the counter virtual object via curl: - -```shell -# To add a new value to my-counter -curl http://localhost:8080/Counter/my-counter/add --json "1" -# To get my-counter value -curl http://localhost:8080/Counter/my-counter/get -``` - -The command assumes that the Restate runtime is reachable under `localhost:8080`. diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts deleted file mode 100644 index c75d67d5..00000000 --- a/examples/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer - -plugins { - java - kotlin("jvm") - kotlin("plugin.serialization") - application - alias(kotlinLibs.plugins.ksp) - id("com.github.johnrengelman.shadow").version("8.1.1") -} - -dependencies { - ksp(project(":sdk-api-kotlin-gen")) - annotationProcessor(project(":sdk-api-gen")) - - implementation(project(":sdk-api")) - implementation(project(":sdk-lambda")) - implementation(project(":sdk-http-vertx")) - implementation(project(":sdk-api-kotlin")) - implementation(project(":sdk-serde-jackson")) - implementation(project(":sdk-workflow-api")) - - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.jsr310) - - implementation(kotlinLibs.kotlinx.coroutines) - implementation(kotlinLibs.kotlinx.serialization.core) - implementation(kotlinLibs.kotlinx.serialization.json) - - implementation(coreLibs.log4j.core) -} - -application { - val mainClassValue: String = - project.findProperty("mainClass")?.toString() ?: "my.restate.sdk.examples.Counter" - mainClass.set(mainClassValue) -} - -tasks.withType { this.enabled = false } - -tasks.withType { transform(ServiceFileTransformer::class.java) } diff --git a/examples/src/main/java/my/restate/sdk/examples/Counter.java b/examples/src/main/java/my/restate/sdk/examples/Counter.java deleted file mode 100644 index 1b014a38..00000000 --- a/examples/src/main/java/my/restate/sdk/examples/Counter.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package my.restate.sdk.examples; - -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.SharedObjectContext; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Shared; -import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@VirtualObject(name = "Counter") -public class Counter { - - private static final Logger LOG = LogManager.getLogger(Counter.class); - - private static final StateKey TOTAL = StateKey.of("total", CoreSerdes.JSON_LONG); - - @Handler - public void reset(ObjectContext ctx) { - ctx.clearAll(); - } - - @Handler - public void add(ObjectContext ctx, Long request) { - long currentValue = ctx.get(TOTAL).orElse(0L); - long newValue = currentValue + request; - ctx.set(TOTAL, newValue); - } - - @Shared - @Handler - public Long get(SharedObjectContext ctx) { - return ctx.get(TOTAL).orElse(0L); - } - - @Handler - public CounterUpdateResult getAndAdd(ObjectContext ctx, Long request) { - LOG.info("Invoked get and add with {}", request); - - long currentValue = ctx.get(TOTAL).orElse(0L); - long newValue = currentValue + request; - ctx.set(TOTAL, newValue); - - return new CounterUpdateResult(newValue, currentValue); - } - - public static void main(String[] args) { - RestateHttpEndpointBuilder.builder().bind(new Counter()).buildAndListen(); - } - - public static class CounterUpdateResult { - private final Long newValue; - private final Long oldValue; - - public CounterUpdateResult(Long newValue, Long oldValue) { - this.newValue = newValue; - this.oldValue = oldValue; - } - - public Long getNewValue() { - return newValue; - } - - public Long getOldValue() { - return oldValue; - } - } -} diff --git a/examples/src/main/java/my/restate/sdk/examples/LambdaHandler.java b/examples/src/main/java/my/restate/sdk/examples/LambdaHandler.java deleted file mode 100644 index b11303ff..00000000 --- a/examples/src/main/java/my/restate/sdk/examples/LambdaHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package my.restate.sdk.examples; - -import dev.restate.sdk.lambda.BaseRestateLambdaHandler; -import dev.restate.sdk.lambda.RestateLambdaEndpointBuilder; -import java.util.Objects; -import java.util.regex.Pattern; - -public class LambdaHandler extends BaseRestateLambdaHandler { - - @Override - public void register(RestateLambdaEndpointBuilder builder) { - for (String serviceClass : - Objects.requireNonNullElse( - System.getenv("LAMBDA_FACTORY_SERVICE_CLASS"), Counter.class.getCanonicalName()) - .split(Pattern.quote(","))) { - if (Counter.class.getCanonicalName().equals(serviceClass)) { - builder.bind(new Counter()); - } else if (CounterKt.class.getCanonicalName().equals(serviceClass)) { - builder.bind(new CounterKt()); - } else { - throw new IllegalArgumentException( - "Bad \"LAMBDA_FACTORY_SERVICE_CLASS\" env: " + serviceClass); - } - } - } -} diff --git a/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java b/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java deleted file mode 100644 index 5a6c84a9..00000000 --- a/examples/src/main/java/my/restate/sdk/examples/LoanWorkflow.java +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package my.restate.sdk.examples; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import dev.restate.sdk.Context; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; -import dev.restate.sdk.annotation.Shared; -import dev.restate.sdk.annotation.Workflow; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; -import dev.restate.sdk.serde.jackson.JacksonSerdes; -import dev.restate.sdk.workflow.DurablePromiseKey; -import dev.restate.sdk.workflow.WorkflowContext; -import dev.restate.sdk.workflow.WorkflowExecutionState; -import dev.restate.sdk.workflow.WorkflowSharedContext; -import java.math.BigDecimal; -import java.time.Duration; -import java.time.Instant; -import java.util.concurrent.TimeoutException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@Workflow -public class LoanWorkflow { - - // --- Data types used by the Loan Worfklow - - enum Status { - SUBMITTED, - WAITING_HUMAN_APPROVAL, - APPROVED, - NOT_APPROVED, - TRANSFER_SUCCEEDED, - TRANSFER_FAILED - } - - public static class LoanRequest { - - private final String customerName; - private final String customerId; - private final String customerBankAccount; - private final BigDecimal amount; - - @JsonCreator - public LoanRequest( - @JsonProperty("customerName") String customerName, - @JsonProperty("customerId") String customerId, - @JsonProperty("customerBankAccount") String customerBankAccount, - @JsonProperty("amount") BigDecimal amount) { - this.customerName = customerName; - this.customerId = customerId; - this.customerBankAccount = customerBankAccount; - this.amount = amount; - } - - public String getCustomerName() { - return customerName; - } - - public String getCustomerId() { - return customerId; - } - - public String getCustomerBankAccount() { - return customerBankAccount; - } - - public BigDecimal getAmount() { - return amount; - } - } - - private static final Logger LOG = LogManager.getLogger(LoanWorkflow.class); - - private static final StateKey STATUS = - StateKey.of("status", JacksonSerdes.of(Status.class)); - private static final StateKey LOAN_REQUEST = - StateKey.of("loanRequest", JacksonSerdes.of(LoanRequest.class)); - private static final DurablePromiseKey HUMAN_APPROVAL = - DurablePromiseKey.of("humanApproval", CoreSerdes.JSON_BOOLEAN); - private static final StateKey TRANSFER_EXECUTION_TIME = - StateKey.of("transferExecutionTime", CoreSerdes.JSON_STRING); - - // --- The main workflow method - - @Workflow - public void run(WorkflowContext ctx, LoanRequest loanRequest) { - // 1. Set status - ctx.set(STATUS, Status.SUBMITTED); - ctx.set(LOAN_REQUEST, loanRequest); - - LOG.info("Loan request submitted"); - - // 2. Ask human approval - ctx.run(() -> askHumanApproval(ctx.workflowKey())); - ctx.set(STATUS, Status.WAITING_HUMAN_APPROVAL); - - // 3. Wait human approval - boolean approved = ctx.durablePromise(HUMAN_APPROVAL).awaitable().await(); - if (!approved) { - LOG.info("Not approved"); - ctx.set(STATUS, Status.NOT_APPROVED); - return; - } - LOG.info("Approved"); - ctx.set(STATUS, Status.APPROVED); - - // 4. Request money transaction to the bank - var bankClient = LoanWorkflowMockBankClient.fromContext(ctx); - Instant executionTime; - try { - executionTime = - bankClient - .transfer( - new TransferRequest( - loanRequest.getCustomerBankAccount(), loanRequest.getAmount())) - .await(Duration.ofDays(7)); - } catch (TerminalException | TimeoutException e) { - LOG.warn("Transaction failed", e); - ctx.set(STATUS, Status.TRANSFER_FAILED); - return; - } - - LOG.info("Transfer complete"); - - // 5. Transfer complete! - ctx.set(TRANSFER_EXECUTION_TIME, executionTime.toString()); - ctx.set(STATUS, Status.TRANSFER_SUCCEEDED); - } - - // --- Methods to approve/reject loan - - @Shared - public void approveLoan(WorkflowSharedContext ctx) { - ctx.durablePromiseHandle(HUMAN_APPROVAL).resolve(true); - } - - @Shared - public void rejectLoan(WorkflowSharedContext ctx) { - ctx.durablePromiseHandle(HUMAN_APPROVAL).resolve(false); - } - - public static void main(String[] args) { - RestateHttpEndpointBuilder.builder() - .bind(new LoanWorkflow()) - .bind(new MockBank()) - .buildAndListen(); - - // Register the service in the meantime! - LOG.info("Now it's time to register this deployment"); - - try { - Thread.sleep(20_000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - // To invoke the workflow: - LoanWorkflowClient.IngressClient client = - LoanWorkflowClient.fromIngress("http://127.0.0.1:8080", "my-loan"); - - WorkflowExecutionState state = - client.submit( - new LoanRequest( - "Francesco", "slinkydeveloper", "DE1234", new BigDecimal("1000000000"))); - if (state != WorkflowExecutionState.STARTED) { - throw new IllegalStateException("Unexpected state " + state); - } - - LOG.info("Started loan workflow"); - - // Takes some bureaucratic time to approve the loan - try { - Thread.sleep(10_000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - LOG.info("We took the decision to approve your loan! You can now achieve your dreams!"); - - // Now approve it - client.approveLoan(); - - while (!client.isCompleted()) { - LOG.info("Not completed yet"); - try { - Thread.sleep(10_000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - LOG.info("Loan workflow completed"); - } - - // -- Some mocks - - private static void askHumanApproval(String workflowKey) throws InterruptedException { - LOG.info("Sending human approval request"); - Thread.sleep(1000); - } - - @Service - static class MockBank { - @Handler - public Instant transfer(Context context, TransferRequest request) throws TerminalException { - boolean shouldAccept = context.random().nextInt(3) != 1; - if (shouldAccept) { - return Instant.now(); - } else { - throw new TerminalException("Won't accept the transfer"); - } - } - } - - public static class TransferRequest { - private final String bankAccount; - private final BigDecimal amount; - - @JsonCreator - public TransferRequest( - @JsonProperty("bankAccount") String bankAccount, - @JsonProperty("amount") BigDecimal amount) { - this.bankAccount = bankAccount; - this.amount = amount; - } - - public String getBankAccount() { - return bankAccount; - } - - public BigDecimal getAmount() { - return amount; - } - } -} diff --git a/examples/src/main/kotlin/my/restate/sdk/examples/CounterKt.kt b/examples/src/main/kotlin/my/restate/sdk/examples/CounterKt.kt deleted file mode 100644 index 9940e3da..00000000 --- a/examples/src/main/kotlin/my/restate/sdk/examples/CounterKt.kt +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package my.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.http.vertx.RestateHttpEndpointBuilder -import dev.restate.sdk.kotlin.KtStateKey -import dev.restate.sdk.kotlin.ObjectContext -import dev.restate.sdk.kotlin.SharedObjectContext -import kotlinx.serialization.Serializable -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.Logger - -@Serializable data class CounterUpdate(var oldValue: Long, val newValue: Long) - -@VirtualObject -class CounterKt { - - companion object { - private val TOTAL = KtStateKey.json("total") - private val LOG: Logger = LogManager.getLogger(CounterKt::class.java) - } - - @Handler - suspend fun reset(ctx: ObjectContext) { - ctx.clear(TOTAL) - } - - @Handler - suspend fun add(ctx: ObjectContext, value: Long) { - val currentValue = ctx.get(TOTAL) ?: 0L - val newValue = currentValue + value - ctx.set(TOTAL, newValue) - } - - @Handler - @Shared - suspend fun get(ctx: SharedObjectContext): Long { - return ctx.get(TOTAL) ?: 0L - } - - @Handler - suspend fun getAndAdd(ctx: ObjectContext, value: Long): CounterUpdate { - LOG.info("Invoked get and add with $value") - val currentValue = ctx.get(TOTAL) ?: 0L - val newValue = currentValue + value - ctx.set(TOTAL, newValue) - return CounterUpdate(currentValue, newValue) - } -} - -fun main() { - RestateHttpEndpointBuilder.builder().bind(CounterKt()).buildAndListen() -} diff --git a/examples/src/main/resources/log4j2.properties b/examples/src/main/resources/log4j2.properties deleted file mode 100644 index 536d1d39..00000000 --- a/examples/src/main/resources/log4j2.properties +++ /dev/null @@ -1,26 +0,0 @@ -# Set to debug or trace if log4j initialization is failing -status = warn - -# Console appender configuration -appender.console.type = Console -appender.console.name = consoleLogger -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %notEmpty{[%X{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/gradle.properties b/gradle.properties deleted file mode 100644 index 2d2f8d81..00000000 --- a/gradle.properties +++ /dev/null @@ -1,5 +0,0 @@ -org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ - --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index 7e8ac7da..00000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,2 +0,0 @@ -[versions] - restate = '0.9.0-SNAPSHOT' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd4917707c1f8861d8cb53dd15194d4248596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43462 zcma&NWl&^owk(X(xVyW%ySuwf;qI=D6|RlDJ2cR^yEKh!@I- zp9QeisK*rlxC>+~7Dk4IxIRsKBHqdR9b3+fyL=ynHmIDe&|>O*VlvO+%z5;9Z$|DJ zb4dO}-R=MKr^6EKJiOrJdLnCJn>np?~vU-1sSFgPu;pthGwf}bG z(1db%xwr#x)r+`4AGu$j7~u2MpVs3VpLp|mx&;>`0p0vH6kF+D2CY0fVdQOZ@h;A` z{infNyvmFUiu*XG}RNMNwXrbec_*a3N=2zJ|Wh5z* z5rAX$JJR{#zP>KY**>xHTuw?|-Rg|o24V)74HcfVT;WtQHXlE+_4iPE8QE#DUm%x0 zEKr75ur~W%w#-My3Tj`hH6EuEW+8K-^5P62$7Sc5OK+22qj&Pd1;)1#4tKihi=~8C zHiQSst0cpri6%OeaR`PY>HH_;CPaRNty%WTm4{wDK8V6gCZlG@U3$~JQZ;HPvDJcT1V{ z?>H@13MJcCNe#5z+MecYNi@VT5|&UiN1D4ATT+%M+h4c$t;C#UAs3O_q=GxK0}8%8 z8J(_M9bayxN}69ex4dzM_P3oh@ZGREjVvn%%r7=xjkqxJP4kj}5tlf;QosR=%4L5y zWhgejO=vao5oX%mOHbhJ8V+SG&K5dABn6!WiKl{|oPkq(9z8l&Mm%(=qGcFzI=eLu zWc_oCLyf;hVlB@dnwY98?75B20=n$>u3b|NB28H0u-6Rpl((%KWEBOfElVWJx+5yg z#SGqwza7f}$z;n~g%4HDU{;V{gXIhft*q2=4zSezGK~nBgu9-Q*rZ#2f=Q}i2|qOp z!!y4p)4o=LVUNhlkp#JL{tfkhXNbB=Ox>M=n6soptJw-IDI|_$is2w}(XY>a=H52d z3zE$tjPUhWWS+5h=KVH&uqQS=$v3nRs&p$%11b%5qtF}S2#Pc`IiyBIF4%A!;AVoI zXU8-Rpv!DQNcF~(qQnyyMy=-AN~U>#&X1j5BLDP{?K!%h!;hfJI>$mdLSvktEr*89 zdJHvby^$xEX0^l9g$xW-d?J;L0#(`UT~zpL&*cEh$L|HPAu=P8`OQZV!-}l`noSp_ zQ-1$q$R-gDL)?6YaM!=8H=QGW$NT2SeZlb8PKJdc=F-cT@j7Xags+Pr*jPtlHFnf- zh?q<6;)27IdPc^Wdy-mX%2s84C1xZq9Xms+==F4);O`VUASmu3(RlgE#0+#giLh-& zcxm3_e}n4{%|X zJp{G_j+%`j_q5}k{eW&TlP}J2wtZ2^<^E(O)4OQX8FDp6RJq!F{(6eHWSD3=f~(h} zJXCf7=r<16X{pHkm%yzYI_=VDP&9bmI1*)YXZeB}F? z(%QsB5fo*FUZxK$oX~X^69;x~j7ms8xlzpt-T15e9}$4T-pC z6PFg@;B-j|Ywajpe4~bk#S6(fO^|mm1hKOPfA%8-_iGCfICE|=P_~e;Wz6my&)h_~ zkv&_xSAw7AZ%ThYF(4jADW4vg=oEdJGVOs>FqamoL3Np8>?!W#!R-0%2Bg4h?kz5I zKV-rKN2n(vUL%D<4oj@|`eJ>0i#TmYBtYmfla;c!ATW%;xGQ0*TW@PTlGG><@dxUI zg>+3SiGdZ%?5N=8uoLA|$4isK$aJ%i{hECP$bK{J#0W2gQ3YEa zZQ50Stn6hqdfxJ*9#NuSLwKFCUGk@c=(igyVL;;2^wi4o30YXSIb2g_ud$ zgpCr@H0qWtk2hK8Q|&wx)}4+hTYlf;$a4#oUM=V@Cw#!$(nOFFpZ;0lc!qd=c$S}Z zGGI-0jg~S~cgVT=4Vo)b)|4phjStD49*EqC)IPwyeKBLcN;Wu@Aeph;emROAwJ-0< z_#>wVm$)ygH|qyxZaet&(Vf%pVdnvKWJn9`%DAxj3ot;v>S$I}jJ$FLBF*~iZ!ZXE zkvui&p}fI0Y=IDX)mm0@tAd|fEHl~J&K}ZX(Mm3cm1UAuwJ42+AO5@HwYfDH7ipIc zmI;1J;J@+aCNG1M`Btf>YT>~c&3j~Qi@Py5JT6;zjx$cvOQW@3oQ>|}GH?TW-E z1R;q^QFjm5W~7f}c3Ww|awg1BAJ^slEV~Pk`Kd`PS$7;SqJZNj->it4DW2l15}xP6 zoCl$kyEF%yJni0(L!Z&14m!1urXh6Btj_5JYt1{#+H8w?5QI%% zo-$KYWNMJVH?Hh@1n7OSu~QhSswL8x0=$<8QG_zepi_`y_79=nK=_ZP_`Em2UI*tyQoB+r{1QYZCpb?2OrgUw#oRH$?^Tj!Req>XiE#~B|~ z+%HB;=ic+R@px4Ld8mwpY;W^A%8%l8$@B@1m5n`TlKI6bz2mp*^^^1mK$COW$HOfp zUGTz-cN9?BGEp}5A!mDFjaiWa2_J2Iq8qj0mXzk; z66JBKRP{p%wN7XobR0YjhAuW9T1Gw3FDvR5dWJ8ElNYF94eF3ebu+QwKjtvVu4L zI9ip#mQ@4uqVdkl-TUQMb^XBJVLW(-$s;Nq;@5gr4`UfLgF$adIhd?rHOa%D);whv z=;krPp~@I+-Z|r#s3yCH+c1US?dnm+C*)r{m+86sTJusLdNu^sqLrfWed^ndHXH`m zd3#cOe3>w-ga(Dus_^ppG9AC>Iq{y%%CK+Cro_sqLCs{VLuK=dev>OL1dis4(PQ5R zcz)>DjEkfV+MO;~>VUlYF00SgfUo~@(&9$Iy2|G0T9BSP?&T22>K46D zL*~j#yJ?)^*%J3!16f)@Y2Z^kS*BzwfAQ7K96rFRIh>#$*$_Io;z>ux@}G98!fWR@ zGTFxv4r~v)Gsd|pF91*-eaZ3Qw1MH$K^7JhWIdX%o$2kCbvGDXy)a?@8T&1dY4`;L z4Kn+f%SSFWE_rpEpL9bnlmYq`D!6F%di<&Hh=+!VI~j)2mfil03T#jJ_s?}VV0_hp z7T9bWxc>Jm2Z0WMU?`Z$xE74Gu~%s{mW!d4uvKCx@WD+gPUQ zV0vQS(Ig++z=EHN)BR44*EDSWIyT~R4$FcF*VEY*8@l=218Q05D2$|fXKFhRgBIEE zdDFB}1dKkoO^7}{5crKX!p?dZWNz$m>1icsXG2N+((x0OIST9Zo^DW_tytvlwXGpn zs8?pJXjEG;T@qrZi%#h93?FP$!&P4JA(&H61tqQi=opRzNpm zkrG}$^t9&XduK*Qa1?355wd8G2CI6QEh@Ua>AsD;7oRUNLPb76m4HG3K?)wF~IyS3`fXuNM>${?wmB zpVz;?6_(Fiadfd{vUCBM*_kt$+F3J+IojI;9L(gc9n3{sEZyzR9o!_mOwFC#tQ{Q~ zP3-`#uK#tP3Q7~Q;4H|wjZHO8h7e4IuBxl&vz2w~D8)w=Wtg31zpZhz%+kzSzL*dV zwp@{WU4i;hJ7c2f1O;7Mz6qRKeASoIv0_bV=i@NMG*l<#+;INk-^`5w@}Dj~;k=|}qM1vq_P z|GpBGe_IKq|LNy9SJhKOQ$c=5L{Dv|Q_lZl=-ky*BFBJLW9&y_C|!vyM~rQx=!vun z?rZJQB5t}Dctmui5i31C_;_}CEn}_W%>oSXtt>@kE1=JW*4*v4tPp;O6 zmAk{)m!)}34pTWg8{i>($%NQ(Tl;QC@J@FfBoc%Gr&m560^kgSfodAFrIjF}aIw)X zoXZ`@IsMkc8_=w%-7`D6Y4e*CG8k%Ud=GXhsTR50jUnm+R*0A(O3UKFg0`K;qp1bl z7``HN=?39ic_kR|^R^~w-*pa?Vj#7|e9F1iRx{GN2?wK!xR1GW!qa=~pjJb-#u1K8 zeR?Y2i-pt}yJq;SCiVHODIvQJX|ZJaT8nO+(?HXbLefulKKgM^B(UIO1r+S=7;kLJ zcH}1J=Px2jsh3Tec&v8Jcbng8;V-`#*UHt?hB(pmOipKwf3Lz8rG$heEB30Sg*2rx zV<|KN86$soN(I!BwO`1n^^uF2*x&vJ$2d$>+`(romzHP|)K_KkO6Hc>_dwMW-M(#S zK(~SiXT1@fvc#U+?|?PniDRm01)f^#55;nhM|wi?oG>yBsa?~?^xTU|fX-R(sTA+5 zaq}-8Tx7zrOy#3*JLIIVsBmHYLdD}!0NP!+ITW+Thn0)8SS!$@)HXwB3tY!fMxc#1 zMp3H?q3eD?u&Njx4;KQ5G>32+GRp1Ee5qMO0lZjaRRu&{W<&~DoJNGkcYF<5(Ab+J zgO>VhBl{okDPn78<%&e2mR{jwVCz5Og;*Z;;3%VvoGo_;HaGLWYF7q#jDX=Z#Ml`H z858YVV$%J|e<1n`%6Vsvq7GmnAV0wW4$5qQ3uR@1i>tW{xrl|ExywIc?fNgYlA?C5 zh$ezAFb5{rQu6i7BSS5*J-|9DQ{6^BVQ{b*lq`xS@RyrsJN?-t=MTMPY;WYeKBCNg z^2|pN!Q^WPJuuO4!|P@jzt&tY1Y8d%FNK5xK(!@`jO2aEA*4 zkO6b|UVBipci?){-Ke=+1;mGlND8)6+P;8sq}UXw2hn;fc7nM>g}GSMWu&v&fqh

iViYT=fZ(|3Ox^$aWPp4a8h24tD<|8-!aK0lHgL$N7Efw}J zVIB!7=T$U`ao1?upi5V4Et*-lTG0XvExbf!ya{cua==$WJyVG(CmA6Of*8E@DSE%L z`V^$qz&RU$7G5mg;8;=#`@rRG`-uS18$0WPN@!v2d{H2sOqP|!(cQ@ zUHo!d>>yFArLPf1q`uBvY32miqShLT1B@gDL4XoVTK&@owOoD)OIHXrYK-a1d$B{v zF^}8D3Y^g%^cnvScOSJR5QNH+BI%d|;J;wWM3~l>${fb8DNPg)wrf|GBP8p%LNGN# z3EaIiItgwtGgT&iYCFy9-LG}bMI|4LdmmJt@V@% zb6B)1kc=T)(|L@0;wr<>=?r04N;E&ef+7C^`wPWtyQe(*pD1pI_&XHy|0gIGHMekd zF_*M4yi6J&Z4LQj65)S zXwdM{SwUo%3SbPwFsHgqF@V|6afT|R6?&S;lw=8% z3}@9B=#JI3@B*#4s!O))~z zc>2_4Q_#&+5V`GFd?88^;c1i7;Vv_I*qt!_Yx*n=;rj!82rrR2rQ8u5(Ejlo{15P% zs~!{%XJ>FmJ})H^I9bn^Re&38H{xA!0l3^89k(oU;bZWXM@kn$#aoS&Y4l^-WEn-fH39Jb9lA%s*WsKJQl?n9B7_~P z-XM&WL7Z!PcoF6_D>V@$CvUIEy=+Z&0kt{szMk=f1|M+r*a43^$$B^MidrT0J;RI` z(?f!O<8UZkm$_Ny$Hth1J#^4ni+im8M9mr&k|3cIgwvjAgjH z8`N&h25xV#v*d$qBX5jkI|xOhQn!>IYZK7l5#^P4M&twe9&Ey@@GxYMxBZq2e7?`q z$~Szs0!g{2fGcp9PZEt|rdQ6bhAgpcLHPz?f-vB?$dc*!9OL?Q8mn7->bFD2Si60* z!O%y)fCdMSV|lkF9w%x~J*A&srMyYY3{=&$}H zGQ4VG_?$2X(0|vT0{=;W$~icCI{b6W{B!Q8xdGhF|D{25G_5_+%s(46lhvNLkik~R z>nr(&C#5wwOzJZQo9m|U<;&Wk!_#q|V>fsmj1g<6%hB{jGoNUPjgJslld>xmODzGjYc?7JSuA?A_QzjDw5AsRgi@Y|Z0{F{!1=!NES-#*f^s4l0Hu zz468))2IY5dmD9pa*(yT5{EyP^G>@ZWumealS-*WeRcZ}B%gxq{MiJ|RyX-^C1V=0 z@iKdrGi1jTe8Ya^x7yyH$kBNvM4R~`fbPq$BzHum-3Zo8C6=KW@||>zsA8-Y9uV5V z#oq-f5L5}V<&wF4@X@<3^C%ptp6+Ce)~hGl`kwj)bsAjmo_GU^r940Z-|`<)oGnh7 zFF0Tde3>ui?8Yj{sF-Z@)yQd~CGZ*w-6p2U<8}JO-sRsVI5dBji`01W8A&3$?}lxBaC&vn0E$c5tW* zX>5(zzZ=qn&!J~KdsPl;P@bmA-Pr8T*)eh_+Dv5=Ma|XSle6t(k8qcgNyar{*ReQ8 zTXwi=8vr>!3Ywr+BhggHDw8ke==NTQVMCK`$69fhzEFB*4+H9LIvdt-#IbhZvpS}} zO3lz;P?zr0*0$%-Rq_y^k(?I{Mk}h@w}cZpMUp|ucs55bcloL2)($u%mXQw({Wzc~ z;6nu5MkjP)0C(@%6Q_I_vsWrfhl7Zpoxw#WoE~r&GOSCz;_ro6i(^hM>I$8y>`!wW z*U^@?B!MMmb89I}2(hcE4zN2G^kwyWCZp5JG>$Ez7zP~D=J^LMjSM)27_0B_X^C(M z`fFT+%DcKlu?^)FCK>QzSnV%IsXVcUFhFdBP!6~se&xxrIxsvySAWu++IrH;FbcY$ z2DWTvSBRfLwdhr0nMx+URA$j3i7_*6BWv#DXfym?ZRDcX9C?cY9sD3q)uBDR3uWg= z(lUIzB)G$Hr!){>E{s4Dew+tb9kvToZp-1&c?y2wn@Z~(VBhqz`cB;{E4(P3N2*nJ z_>~g@;UF2iG{Kt(<1PyePTKahF8<)pozZ*xH~U-kfoAayCwJViIrnqwqO}7{0pHw$ zs2Kx?s#vQr7XZ264>5RNKSL8|Ty^=PsIx^}QqOOcfpGUU4tRkUc|kc7-!Ae6!+B{o~7nFpm3|G5^=0#Bnm6`V}oSQlrX(u%OWnC zoLPy&Q;1Jui&7ST0~#+}I^&?vcE*t47~Xq#YwvA^6^} z`WkC)$AkNub|t@S!$8CBlwbV~?yp&@9h{D|3z-vJXgzRC5^nYm+PyPcgRzAnEi6Q^gslXYRv4nycsy-SJu?lMps-? zV`U*#WnFsdPLL)Q$AmD|0`UaC4ND07+&UmOu!eHruzV|OUox<+Jl|Mr@6~C`T@P%s zW7sgXLF2SSe9Fl^O(I*{9wsFSYb2l%-;&Pi^dpv!{)C3d0AlNY6!4fgmSgj_wQ*7Am7&$z;Jg&wgR-Ih;lUvWS|KTSg!&s_E9_bXBkZvGiC6bFKDWZxsD$*NZ#_8bl zG1P-#@?OQzED7@jlMJTH@V!6k;W>auvft)}g zhoV{7$q=*;=l{O>Q4a@ ziMjf_u*o^PsO)#BjC%0^h>Xp@;5$p{JSYDt)zbb}s{Kbt!T*I@Pk@X0zds6wsefuU zW$XY%yyRGC94=6mf?x+bbA5CDQ2AgW1T-jVAJbm7K(gp+;v6E0WI#kuACgV$r}6L? zd|Tj?^%^*N&b>Dd{Wr$FS2qI#Ucs1yd4N+RBUQiSZGujH`#I)mG&VKoDh=KKFl4=G z&MagXl6*<)$6P}*Tiebpz5L=oMaPrN+caUXRJ`D?=K9!e0f{@D&cZLKN?iNP@X0aF zE(^pl+;*T5qt?1jRC=5PMgV!XNITRLS_=9{CJExaQj;lt!&pdzpK?8p>%Mb+D z?yO*uSung=-`QQ@yX@Hyd4@CI^r{2oiu`%^bNkz+Nkk!IunjwNC|WcqvX~k=><-I3 zDQdbdb|!v+Iz01$w@aMl!R)koD77Xp;eZwzSl-AT zr@Vu{=xvgfq9akRrrM)}=!=xcs+U1JO}{t(avgz`6RqiiX<|hGG1pmop8k6Q+G_mv zJv|RfDheUp2L3=^C=4aCBMBn0aRCU(DQwX-W(RkRwmLeuJYF<0urcaf(=7)JPg<3P zQs!~G)9CT18o!J4{zX{_e}4eS)U-E)0FAt}wEI(c0%HkxgggW;(1E=>J17_hsH^sP z%lT0LGgbUXHx-K*CI-MCrP66UP0PvGqM$MkeLyqHdbgP|_Cm!7te~b8p+e6sQ_3k| zVcwTh6d83ltdnR>D^)BYQpDKlLk3g0Hdcgz2}%qUs9~~Rie)A-BV1mS&naYai#xcZ z(d{8=-LVpTp}2*y)|gR~;qc7fp26}lPcLZ#=JpYcn3AT9(UIdOyg+d(P5T7D&*P}# zQCYplZO5|7+r19%9e`v^vfSS1sbX1c%=w1;oyruXB%Kl$ACgKQ6=qNWLsc=28xJjg zwvsI5-%SGU|3p>&zXVl^vVtQT3o-#$UT9LI@Npz~6=4!>mc431VRNN8od&Ul^+G_kHC`G=6WVWM z%9eWNyy(FTO|A+@x}Ou3CH)oi;t#7rAxdIXfNFwOj_@Y&TGz6P_sqiB`Q6Lxy|Q{`|fgmRG(k+!#b*M+Z9zFce)f-7;?Km5O=LHV9f9_87; zF7%R2B+$?@sH&&-$@tzaPYkw0;=i|;vWdI|Wl3q_Zu>l;XdIw2FjV=;Mq5t1Q0|f< zs08j54Bp`3RzqE=2enlkZxmX6OF+@|2<)A^RNQpBd6o@OXl+i)zO%D4iGiQNuXd+zIR{_lb96{lc~bxsBveIw6umhShTX+3@ZJ=YHh@ zWY3(d0azg;7oHn>H<>?4@*RQbi>SmM=JrHvIG(~BrvI)#W(EAeO6fS+}mxxcc+X~W6&YVl86W9WFSS}Vz-f9vS?XUDBk)3TcF z8V?$4Q)`uKFq>xT=)Y9mMFVTUk*NIA!0$?RP6Ig0TBmUFrq*Q-Agq~DzxjStQyJ({ zBeZ;o5qUUKg=4Hypm|}>>L=XKsZ!F$yNTDO)jt4H0gdQ5$f|d&bnVCMMXhNh)~mN z@_UV6D7MVlsWz+zM+inZZp&P4fj=tm6fX)SG5H>OsQf_I8c~uGCig$GzuwViK54bcgL;VN|FnyQl>Ed7(@>=8$a_UKIz|V6CeVSd2(P z0Uu>A8A+muM%HLFJQ9UZ5c)BSAv_zH#1f02x?h9C}@pN@6{>UiAp>({Fn(T9Q8B z^`zB;kJ5b`>%dLm+Ol}ty!3;8f1XDSVX0AUe5P#@I+FQ-`$(a;zNgz)4x5hz$Hfbg z!Q(z26wHLXko(1`;(BAOg_wShpX0ixfWq3ponndY+u%1gyX)_h=v1zR#V}#q{au6; z!3K=7fQwnRfg6FXtNQmP>`<;!N137paFS%y?;lb1@BEdbvQHYC{976l`cLqn;b8lp zIDY>~m{gDj(wfnK!lpW6pli)HyLEiUrNc%eXTil|F2s(AY+LW5hkKb>TQ3|Q4S9rr zpDs4uK_co6XPsn_z$LeS{K4jFF`2>U`tbgKdyDne`xmR<@6AA+_hPNKCOR-Zqv;xk zu5!HsBUb^!4uJ7v0RuH-7?l?}b=w5lzzXJ~gZcxRKOovSk@|#V+MuX%Y+=;14i*%{)_gSW9(#4%)AV#3__kac1|qUy!uyP{>?U#5wYNq}y$S9pCc zFc~4mgSC*G~j0u#qqp9 z${>3HV~@->GqEhr_Xwoxq?Hjn#=s2;i~g^&Hn|aDKpA>Oc%HlW(KA1?BXqpxB;Ydx)w;2z^MpjJ(Qi(X!$5RC z*P{~%JGDQqojV>2JbEeCE*OEu!$XJ>bWA9Oa_Hd;y)F%MhBRi*LPcdqR8X`NQ&1L# z5#9L*@qxrx8n}LfeB^J{%-?SU{FCwiWyHp682F+|pa+CQa3ZLzBqN1{)h4d6+vBbV zC#NEbQLC;}me3eeYnOG*nXOJZEU$xLZ1<1Y=7r0(-U0P6-AqwMAM`a(Ed#7vJkn6plb4eI4?2y3yOTGmmDQ!z9`wzbf z_OY#0@5=bnep;MV0X_;;SJJWEf^E6Bd^tVJ9znWx&Ks8t*B>AM@?;D4oWUGc z!H*`6d7Cxo6VuyS4Eye&L1ZRhrRmN6Lr`{NL(wDbif|y&z)JN>Fl5#Wi&mMIr5i;x zBx}3YfF>>8EC(fYnmpu~)CYHuHCyr5*`ECap%t@y=jD>!_%3iiE|LN$mK9>- zHdtpy8fGZtkZF?%TW~29JIAfi2jZT8>OA7=h;8T{{k?c2`nCEx9$r zS+*&vt~2o^^J+}RDG@+9&M^K*z4p{5#IEVbz`1%`m5c2};aGt=V?~vIM}ZdPECDI)47|CWBCfDWUbxBCnmYivQ*0Nu_xb*C>~C9(VjHM zxe<*D<#dQ8TlpMX2c@M<9$w!RP$hpG4cs%AI){jp*Sj|*`m)5(Bw*A0$*i-(CA5#%>a)$+jI2C9r6|(>J8InryENI z$NohnxDUB;wAYDwrb*!N3noBTKPpPN}~09SEL18tkG zxgz(RYU_;DPT{l?Q$+eaZaxnsWCA^ds^0PVRkIM%bOd|G2IEBBiz{&^JtNsODs;5z zICt_Zj8wo^KT$7Bg4H+y!Df#3mbl%%?|EXe!&(Vmac1DJ*y~3+kRKAD=Ovde4^^%~ zw<9av18HLyrf*_>Slp;^i`Uy~`mvBjZ|?Ad63yQa#YK`4+c6;pW4?XIY9G1(Xh9WO8{F-Aju+nS9Vmv=$Ac0ienZ+p9*O%NG zMZKy5?%Z6TAJTE?o5vEr0r>f>hb#2w2U3DL64*au_@P!J!TL`oH2r*{>ffu6|A7tv zL4juf$DZ1MW5ZPsG!5)`k8d8c$J$o;%EIL0va9&GzWvkS%ZsGb#S(?{!UFOZ9<$a| zY|a+5kmD5N&{vRqkgY>aHsBT&`rg|&kezoD)gP0fsNYHsO#TRc_$n6Lf1Z{?+DLziXlHrq4sf(!>O{?Tj;Eh@%)+nRE_2VxbN&&%%caU#JDU%vL3}Cb zsb4AazPI{>8H&d=jUaZDS$-0^AxE@utGs;-Ez_F(qC9T=UZX=>ok2k2 ziTn{K?y~a5reD2A)P${NoI^>JXn>`IeArow(41c-Wm~)wiryEP(OS{YXWi7;%dG9v zI?mwu1MxD{yp_rrk!j^cKM)dc4@p4Ezyo%lRN|XyD}}>v=Xoib0gOcdXrQ^*61HNj z=NP|pd>@yfvr-=m{8$3A8TQGMTE7g=z!%yt`8`Bk-0MMwW~h^++;qyUP!J~ykh1GO z(FZ59xuFR$(WE;F@UUyE@Sp>`aVNjyj=Ty>_Vo}xf`e7`F;j-IgL5`1~-#70$9_=uBMq!2&1l zomRgpD58@)YYfvLtPW}{C5B35R;ZVvB<<#)x%srmc_S=A7F@DW8>QOEGwD6suhwCg z>Pa+YyULhmw%BA*4yjDp|2{!T98~<6Yfd(wo1mQ!KWwq0eg+6)o1>W~f~kL<-S+P@$wx*zeI|1t7z#Sxr5 zt6w+;YblPQNplq4Z#T$GLX#j6yldXAqj>4gAnnWtBICUnA&-dtnlh=t0Ho_vEKwV` z)DlJi#!@nkYV#$!)@>udAU*hF?V`2$Hf=V&6PP_|r#Iv*J$9)pF@X3`k;5})9^o4y z&)~?EjX5yX12O(BsFy-l6}nYeuKkiq`u9145&3Ssg^y{5G3Pse z9w(YVa0)N-fLaBq1`P!_#>SS(8fh_5!f{UrgZ~uEdeMJIz7DzI5!NHHqQtm~#CPij z?=N|J>nPR6_sL7!f4hD_|KH`vf8(Wpnj-(gPWH+ZvID}%?~68SwhPTC3u1_cB`otq z)U?6qo!ZLi5b>*KnYHWW=3F!p%h1;h{L&(Q&{qY6)_qxNfbP6E3yYpW!EO+IW3?@J z);4>g4gnl^8klu7uA>eGF6rIGSynacogr)KUwE_R4E5Xzi*Qir@b-jy55-JPC8c~( zo!W8y9OGZ&`xmc8;=4-U9=h{vCqfCNzYirONmGbRQlR`WWlgnY+1wCXbMz&NT~9*| z6@FrzP!LX&{no2!Ln_3|I==_4`@}V?4a;YZKTdw;vT<+K+z=uWbW(&bXEaWJ^W8Td z-3&1bY^Z*oM<=M}LVt>_j+p=2Iu7pZmbXrhQ_k)ysE9yXKygFNw$5hwDn(M>H+e1&9BM5!|81vd%r%vEm zqxY3?F@fb6O#5UunwgAHR9jp_W2zZ}NGp2%mTW@(hz7$^+a`A?mb8|_G*GNMJ) zjqegXQio=i@AINre&%ofexAr95aop5C+0MZ0m-l=MeO8m3epm7U%vZB8+I+C*iNFM z#T3l`gknX;D$-`2XT^Cg*vrv=RH+P;_dfF++cP?B_msQI4j+lt&rX2)3GaJx%W*Nn zkML%D{z5tpHH=dksQ*gzc|}gzW;lwAbxoR07VNgS*-c3d&8J|;@3t^ zVUz*J*&r7DFRuFVDCJDK8V9NN5hvpgGjwx+5n)qa;YCKe8TKtdnh{I7NU9BCN!0dq zczrBk8pE{{@vJa9ywR@mq*J=v+PG;?fwqlJVhijG!3VmIKs>9T6r7MJpC)m!Tc#>g zMtVsU>wbwFJEfwZ{vB|ZlttNe83)$iz`~#8UJ^r)lJ@HA&G#}W&ZH*;k{=TavpjWE z7hdyLZPf*X%Gm}i`Y{OGeeu^~nB8=`{r#TUrM-`;1cBvEd#d!kPqIgYySYhN-*1;L z^byj%Yi}Gx)Wnkosi337BKs}+5H5dth1JA{Ir-JKN$7zC)*}hqeoD(WfaUDPT>0`- z(6sa0AoIqASwF`>hP}^|)a_j2s^PQn*qVC{Q}htR z5-)duBFXT_V56-+UohKXlq~^6uf!6sA#ttk1o~*QEy_Y-S$gAvq47J9Vtk$5oA$Ct zYhYJ@8{hsC^98${!#Ho?4y5MCa7iGnfz}b9jE~h%EAAv~Qxu)_rAV;^cygV~5r_~?l=B`zObj7S=H=~$W zPtI_m%g$`kL_fVUk9J@>EiBH zOO&jtn~&`hIFMS5S`g8w94R4H40mdNUH4W@@XQk1sr17b{@y|JB*G9z1|CrQjd+GX z6+KyURG3;!*BQrentw{B2R&@2&`2}n(z-2&X7#r!{yg@Soy}cRD~j zj9@UBW+N|4HW4AWapy4wfUI- zZ`gSL6DUlgj*f1hSOGXG0IVH8HxK?o2|3HZ;KW{K+yPAlxtb)NV_2AwJm|E)FRs&& z=c^e7bvUsztY|+f^k7NXs$o1EUq>cR7C0$UKi6IooHWlK_#?IWDkvywnzg&ThWo^? z2O_N{5X39#?eV9l)xI(>@!vSB{DLt*oY!K1R8}_?%+0^C{d9a%N4 zoxHVT1&Lm|uDX%$QrBun5e-F`HJ^T$ zmzv)p@4ZHd_w9!%Hf9UYNvGCw2TTTbrj9pl+T9%-_-}L(tES>Or-}Z4F*{##n3~L~TuxjirGuIY#H7{%$E${?p{Q01 zi6T`n;rbK1yIB9jmQNycD~yZq&mbIsFWHo|ZAChSFPQa<(%d8mGw*V3fh|yFoxOOiWJd(qvVb!Z$b88cg->N=qO*4k~6;R==|9ihg&riu#P~s4Oap9O7f%crSr^rljeIfXDEg>wi)&v*a%7zpz<9w z*r!3q9J|390x`Zk;g$&OeN&ctp)VKRpDSV@kU2Q>jtok($Y-*x8_$2piTxun81@vt z!Vj?COa0fg2RPXMSIo26T=~0d`{oGP*eV+$!0I<(4azk&Vj3SiG=Q!6mX0p$z7I}; z9BJUFgT-K9MQQ-0@Z=^7R<{bn2Fm48endsSs`V7_@%8?Bxkqv>BDoVcj?K#dV#uUP zL1ND~?D-|VGKe3Rw_7-Idpht>H6XRLh*U7epS6byiGvJpr%d}XwfusjH9g;Z98H`x zyde%%5mhGOiL4wljCaWCk-&uE4_OOccb9c!ZaWt4B(wYl!?vyzl%7n~QepN&eFUrw zFIOl9c({``6~QD+43*_tzP{f2x41h(?b43^y6=iwyB)2os5hBE!@YUS5?N_tXd=h( z)WE286Fbd>R4M^P{!G)f;h<3Q>Fipuy+d2q-)!RyTgt;wr$(?9ox3;q+{E*ZQHhOn;lM`cjnu9 zXa48ks-v(~b*;MAI<>YZH(^NV8vjb34beE<_cwKlJoR;k6lJNSP6v}uiyRD?|0w+X@o1ONrH8a$fCxXpf? z?$DL0)7|X}Oc%h^zrMKWc-NS9I0Utu@>*j}b@tJ=ixQSJ={4@854wzW@E>VSL+Y{i z#0b=WpbCZS>kUCO_iQz)LoE>P5LIG-hv9E+oG}DtlIDF>$tJ1aw9^LuhLEHt?BCj& z(O4I8v1s#HUi5A>nIS-JK{v!7dJx)^Yg%XjNmlkWAq2*cv#tHgz`Y(bETc6CuO1VkN^L-L3j_x<4NqYb5rzrLC-7uOv z!5e`GZt%B782C5-fGnn*GhDF$%(qP<74Z}3xx+{$4cYKy2ikxI7B2N+2r07DN;|-T->nU&!=Cm#rZt%O_5c&1Z%nlWq3TKAW0w zQqemZw_ue--2uKQsx+niCUou?HjD`xhEjjQd3%rrBi82crq*~#uA4+>vR<_S{~5ce z-2EIl?~s z1=GVL{NxP1N3%=AOaC}j_Fv=ur&THz zyO!d9kHq|c73kpq`$+t+8Bw7MgeR5~`d7ChYyGCBWSteTB>8WAU(NPYt2Dk`@#+}= zI4SvLlyk#pBgVigEe`?NG*vl7V6m+<}%FwPV=~PvvA)=#ths==DRTDEYh4V5}Cf$z@#;< zyWfLY_5sP$gc3LLl2x+Ii)#b2nhNXJ{R~vk`s5U7Nyu^3yFg&D%Txwj6QezMX`V(x z=C`{76*mNb!qHHs)#GgGZ_7|vkt9izl_&PBrsu@}L`X{95-2jf99K)0=*N)VxBX2q z((vkpP2RneSIiIUEnGb?VqbMb=Zia+rF~+iqslydE34cSLJ&BJW^3knX@M;t*b=EA zNvGzv41Ld_T+WT#XjDB840vovUU^FtN_)G}7v)1lPetgpEK9YS^OWFkPoE{ovj^=@ zO9N$S=G$1ecndT_=5ehth2Lmd1II-PuT~C9`XVePw$y8J#dpZ?Tss<6wtVglm(Ok7 z3?^oi@pPio6l&!z8JY(pJvG=*pI?GIOu}e^EB6QYk$#FJQ%^AIK$I4epJ+9t?KjqA+bkj&PQ*|vLttme+`9G=L% ziadyMw_7-M)hS(3E$QGNCu|o23|%O+VN7;Qggp?PB3K-iSeBa2b}V4_wY`G1Jsfz4 z9|SdB^;|I8E8gWqHKx!vj_@SMY^hLEIbSMCuE?WKq=c2mJK z8LoG-pnY!uhqFv&L?yEuxo{dpMTsmCn)95xanqBrNPTgXP((H$9N${Ow~Is-FBg%h z53;|Y5$MUN)9W2HBe2TD`ct^LHI<(xWrw}$qSoei?}s)&w$;&!14w6B6>Yr6Y8b)S z0r71`WmAvJJ`1h&poLftLUS6Ir zC$bG9!Im_4Zjse)#K=oJM9mHW1{%l8sz$1o?ltdKlLTxWWPB>Vk22czVt|1%^wnN@*!l)}?EgtvhC>vlHm^t+ogpgHI1_$1ox9e;>0!+b(tBrmXRB`PY1vp-R**8N7 zGP|QqI$m(Rdu#=(?!(N}G9QhQ%o!aXE=aN{&wtGP8|_qh+7a_j_sU5|J^)vxq;# zjvzLn%_QPHZZIWu1&mRAj;Sa_97p_lLq_{~j!M9N^1yp3U_SxRqK&JnR%6VI#^E12 z>CdOVI^_9aPK2eZ4h&^{pQs}xsijXgFYRIxJ~N7&BB9jUR1fm!(xl)mvy|3e6-B3j zJn#ajL;bFTYJ2+Q)tDjx=3IklO@Q+FFM}6UJr6km7hj7th9n_&JR7fnqC!hTZoM~T zBeaVFp%)0cbPhejX<8pf5HyRUj2>aXnXBqDJe73~J%P(2C?-RT{c3NjE`)om! zl$uewSgWkE66$Kb34+QZZvRn`fob~Cl9=cRk@Es}KQm=?E~CE%spXaMO6YmrMl%9Q zlA3Q$3|L1QJ4?->UjT&CBd!~ru{Ih^in&JXO=|<6J!&qp zRe*OZ*cj5bHYlz!!~iEKcuE|;U4vN1rk$xq6>bUWD*u(V@8sG^7>kVuo(QL@Ki;yL zWC!FT(q{E8#on>%1iAS0HMZDJg{Z{^!De(vSIq&;1$+b)oRMwA3nc3mdTSG#3uYO_ z>+x;7p4I;uHz?ZB>dA-BKl+t-3IB!jBRgdvAbW!aJ(Q{aT>+iz?91`C-xbe)IBoND z9_Xth{6?(y3rddwY$GD65IT#f3<(0o#`di{sh2gm{dw*#-Vnc3r=4==&PU^hCv$qd zjw;>i&?L*Wq#TxG$mFIUf>eK+170KG;~+o&1;Tom9}}mKo23KwdEM6UonXgc z!6N(@k8q@HPw{O8O!lAyi{rZv|DpgfU{py+j(X_cwpKqcalcqKIr0kM^%Br3SdeD> zHSKV94Yxw;pjzDHo!Q?8^0bb%L|wC;4U^9I#pd5O&eexX+Im{ z?jKnCcsE|H?{uGMqVie_C~w7GX)kYGWAg%-?8|N_1#W-|4F)3YTDC+QSq1s!DnOML3@d`mG%o2YbYd#jww|jD$gotpa)kntakp#K;+yo-_ZF9qrNZw<%#C zuPE@#3RocLgPyiBZ+R_-FJ_$xP!RzWm|aN)S+{$LY9vvN+IW~Kf3TsEIvP+B9Mtm! zpfNNxObWQpLoaO&cJh5>%slZnHl_Q~(-Tfh!DMz(dTWld@LG1VRF`9`DYKhyNv z2pU|UZ$#_yUx_B_|MxUq^glT}O5Xt(Vm4Mr02><%C)@v;vPb@pT$*yzJ4aPc_FZ3z z3}PLoMBIM>q_9U2rl^sGhk1VUJ89=*?7|v`{!Z{6bqFMq(mYiA?%KbsI~JwuqVA9$H5vDE+VocjX+G^%bieqx->s;XWlKcuv(s%y%D5Xbc9+ zc(_2nYS1&^yL*ey664&4`IoOeDIig}y-E~_GS?m;D!xv5-xwz+G`5l6V+}CpeJDi^ z%4ed$qowm88=iYG+(`ld5Uh&>Dgs4uPHSJ^TngXP_V6fPyl~>2bhi20QB%lSd#yYn zO05?KT1z@?^-bqO8Cg`;ft>ilejsw@2%RR7;`$Vs;FmO(Yr3Fp`pHGr@P2hC%QcA|X&N2Dn zYf`MqXdHi%cGR@%y7Rg7?d3?an){s$zA{!H;Ie5exE#c~@NhQUFG8V=SQh%UxUeiV zd7#UcYqD=lk-}sEwlpu&H^T_V0{#G?lZMxL7ih_&{(g)MWBnCZxtXg znr#}>U^6!jA%e}@Gj49LWG@*&t0V>Cxc3?oO7LSG%~)Y5}f7vqUUnQ;STjdDU}P9IF9d9<$;=QaXc zL1^X7>fa^jHBu_}9}J~#-oz3Oq^JmGR#?GO7b9a(=R@fw@}Q{{@`Wy1vIQ#Bw?>@X z-_RGG@wt|%u`XUc%W{J z>iSeiz8C3H7@St3mOr_mU+&bL#Uif;+Xw-aZdNYUpdf>Rvu0i0t6k*}vwU`XNO2he z%miH|1tQ8~ZK!zmL&wa3E;l?!!XzgV#%PMVU!0xrDsNNZUWKlbiOjzH-1Uoxm8E#r`#2Sz;-o&qcqB zC-O_R{QGuynW14@)7&@yw1U}uP(1cov)twxeLus0s|7ayrtT8c#`&2~Fiu2=R;1_4bCaD=*E@cYI>7YSnt)nQc zohw5CsK%m?8Ack)qNx`W0_v$5S}nO|(V|RZKBD+btO?JXe|~^Qqur%@eO~<8-L^9d z=GA3-V14ng9L29~XJ>a5k~xT2152zLhM*@zlp2P5Eu}bywkcqR;ISbas&#T#;HZSf z2m69qTV(V@EkY(1Dk3`}j)JMo%ZVJ*5eB zYOjIisi+igK0#yW*gBGj?@I{~mUOvRFQR^pJbEbzFxTubnrw(Muk%}jI+vXmJ;{Q6 zrSobKD>T%}jV4Ub?L1+MGOD~0Ir%-`iTnWZN^~YPrcP5y3VMAzQ+&en^VzKEb$K!Q z<7Dbg&DNXuow*eD5yMr+#08nF!;%4vGrJI++5HdCFcGLfMW!KS*Oi@=7hFwDG!h2< zPunUEAF+HncQkbfFj&pbzp|MU*~60Z(|Ik%Tn{BXMN!hZOosNIseT?R;A`W?=d?5X zK(FB=9mZusYahp|K-wyb={rOpdn=@;4YI2W0EcbMKyo~-#^?h`BA9~o285%oY zfifCh5Lk$SY@|2A@a!T2V+{^!psQkx4?x0HSV`(w9{l75QxMk!)U52Lbhn{8ol?S) zCKo*7R(z!uk<6*qO=wh!Pul{(qq6g6xW;X68GI_CXp`XwO zxuSgPRAtM8K7}5E#-GM!*ydOOG_{A{)hkCII<|2=ma*71ci_-}VPARm3crFQjLYV! z9zbz82$|l01mv`$WahE2$=fAGWkd^X2kY(J7iz}WGS z@%MyBEO=A?HB9=^?nX`@nh;7;laAjs+fbo!|K^mE!tOB>$2a_O0y-*uaIn8k^6Y zSbuv;5~##*4Y~+y7Z5O*3w4qgI5V^17u*ZeupVGH^nM&$qmAk|anf*>r zWc5CV;-JY-Z@Uq1Irpb^O`L_7AGiqd*YpGUShb==os$uN3yYvb`wm6d=?T*it&pDk zo`vhw)RZX|91^^Wa_ti2zBFyWy4cJu#g)_S6~jT}CC{DJ_kKpT`$oAL%b^!2M;JgT zM3ZNbUB?}kP(*YYvXDIH8^7LUxz5oE%kMhF!rnPqv!GiY0o}NR$OD=ITDo9r%4E>E0Y^R(rS^~XjWyVI6 zMOR5rPXhTp*G*M&X#NTL`Hu*R+u*QNoiOKg4CtNPrjgH>c?Hi4MUG#I917fx**+pJfOo!zFM&*da&G_x)L(`k&TPI*t3e^{crd zX<4I$5nBQ8Ax_lmNRa~E*zS-R0sxkz`|>7q_?*e%7bxqNm3_eRG#1ae3gtV9!fQpY z+!^a38o4ZGy9!J5sylDxZTx$JmG!wg7;>&5H1)>f4dXj;B+@6tMlL=)cLl={jLMxY zbbf1ax3S4>bwB9-$;SN2?+GULu;UA-35;VY*^9Blx)Jwyb$=U!D>HhB&=jSsd^6yw zL)?a|>GxU!W}ocTC(?-%z3!IUhw^uzc`Vz_g>-tv)(XA#JK^)ZnC|l1`@CdX1@|!| z_9gQ)7uOf?cR@KDp97*>6X|;t@Y`k_N@)aH7gY27)COv^P3ya9I{4z~vUjLR9~z1Z z5=G{mVtKH*&$*t0@}-i_v|3B$AHHYale7>E+jP`ClqG%L{u;*ff_h@)al?RuL7tOO z->;I}>%WI{;vbLP3VIQ^iA$4wl6@0sDj|~112Y4OFjMs`13!$JGkp%b&E8QzJw_L5 zOnw9joc0^;O%OpF$Qp)W1HI!$4BaXX84`%@#^dk^hFp^pQ@rx4g(8Xjy#!X%+X5Jd@fs3amGT`}mhq#L97R>OwT5-m|h#yT_-v@(k$q7P*9X~T*3)LTdzP!*B} z+SldbVWrrwQo9wX*%FyK+sRXTa@O?WM^FGWOE?S`R(0P{<6p#f?0NJvnBia?k^fX2 zNQs7K-?EijgHJY}&zsr;qJ<*PCZUd*x|dD=IQPUK_nn)@X4KWtqoJNHkT?ZWL_hF? zS8lp2(q>;RXR|F;1O}EE#}gCrY~#n^O`_I&?&z5~7N;zL0)3Tup`%)oHMK-^r$NT% zbFg|o?b9w(q@)6w5V%si<$!U<#}s#x@0aX-hP>zwS#9*75VXA4K*%gUc>+yzupTDBOKH8WR4V0pM(HrfbQ&eJ79>HdCvE=F z|J>s;;iDLB^3(9}?biKbxf1$lI!*Z%*0&8UUq}wMyPs_hclyQQi4;NUY+x2qy|0J; zhn8;5)4ED1oHwg+VZF|80<4MrL97tGGXc5Sw$wAI#|2*cvQ=jB5+{AjMiDHmhUC*a zlmiZ`LAuAn_}hftXh;`Kq0zblDk8?O-`tnilIh|;3lZp@F_osJUV9`*R29M?7H{Fy z`nfVEIDIWXmU&YW;NjU8)EJpXhxe5t+scf|VXM!^bBlwNh)~7|3?fWwo_~ZFk(22% zTMesYw+LNx3J-_|DM~`v93yXe=jPD{q;li;5PD?Dyk+b? zo21|XpT@)$BM$%F=P9J19Vi&1#{jM3!^Y&fr&_`toi`XB1!n>sbL%U9I5<7!@?t)~ z;&H%z>bAaQ4f$wIzkjH70;<8tpUoxzKrPhn#IQfS%9l5=Iu))^XC<58D!-O z{B+o5R^Z21H0T9JQ5gNJnqh#qH^na|z92=hONIM~@_iuOi|F>jBh-?aA20}Qx~EpDGElELNn~|7WRXRFnw+Wdo`|# zBpU=Cz3z%cUJ0mx_1($X<40XEIYz(`noWeO+x#yb_pwj6)R(__%@_Cf>txOQ74wSJ z0#F3(zWWaR-jMEY$7C*3HJrohc79>MCUu26mfYN)f4M~4gD`}EX4e}A!U}QV8!S47 z6y-U-%+h`1n`*pQuKE%Av0@)+wBZr9mH}@vH@i{v(m-6QK7Ncf17x_D=)32`FOjjo zg|^VPf5c6-!FxN{25dvVh#fog=NNpXz zfB$o+0jbRkHH{!TKhE709f+jI^$3#v1Nmf80w`@7-5$1Iv_`)W^px8P-({xwb;D0y z7LKDAHgX<84?l!I*Dvi2#D@oAE^J|g$3!)x1Ua;_;<@#l1fD}lqU2_tS^6Ht$1Wl} zBESo7o^)9-Tjuz$8YQSGhfs{BQV6zW7dA?0b(Dbt=UnQs&4zHfe_sj{RJ4uS-vQpC zX;Bbsuju4%!o8?&m4UZU@~ZZjeFF6ex2ss5_60_JS_|iNc+R0GIjH1@Z z=rLT9%B|WWgOrR7IiIwr2=T;Ne?30M!@{%Qf8o`!>=s<2CBpCK_TWc(DX51>e^xh8 z&@$^b6CgOd7KXQV&Y4%}_#uN*mbanXq(2=Nj`L7H7*k(6F8s6{FOw@(DzU`4-*77{ zF+dxpv}%mFpYK?>N_2*#Y?oB*qEKB}VoQ@bzm>ptmVS_EC(#}Lxxx730trt0G)#$b zE=wVvtqOct1%*9}U{q<)2?{+0TzZzP0jgf9*)arV)*e!f`|jgT{7_9iS@e)recI#z zbzolURQ+TOzE!ymqvBY7+5NnAbWxvMLsLTwEbFqW=CPyCsmJ}P1^V30|D5E|p3BC5 z)3|qgw@ra7aXb-wsa|l^in~1_fm{7bS9jhVRkYVO#U{qMp z)Wce+|DJ}4<2gp8r0_xfZpMo#{Hl2MfjLcZdRB9(B(A(f;+4s*FxV{1F|4d`*sRNd zp4#@sEY|?^FIJ;tmH{@keZ$P(sLh5IdOk@k^0uB^BWr@pk6mHy$qf&~rI>P*a;h0C{%oA*i!VjWn&D~O#MxN&f@1Po# zKN+ zrGrkSjcr?^R#nGl<#Q722^wbYcgW@{+6CBS<1@%dPA8HC!~a`jTz<`g_l5N1M@9wn9GOAZ>nqNgq!yOCbZ@1z`U_N`Z>}+1HIZxk*5RDc&rd5{3qjRh8QmT$VyS;jK z;AF+r6XnnCp=wQYoG|rT2@8&IvKq*IB_WvS%nt%e{MCFm`&W*#LXc|HrD?nVBo=(8*=Aq?u$sDA_sC_RPDUiQ+wnIJET8vx$&fxkW~kP9qXKt zozR)@xGC!P)CTkjeWvXW5&@2?)qt)jiYWWBU?AUtzAN}{JE1I)dfz~7$;}~BmQF`k zpn11qmObXwRB8&rnEG*#4Xax3XBkKlw(;tb?Np^i+H8m(Wyz9k{~ogba@laiEk;2! zV*QV^6g6(QG%vX5Um#^sT&_e`B1pBW5yVth~xUs#0}nv?~C#l?W+9Lsb_5)!71rirGvY zTIJ$OPOY516Y|_014sNv+Z8cc5t_V=i>lWV=vNu#!58y9Zl&GsMEW#pPYPYGHQ|;vFvd*9eM==$_=vc7xnyz0~ zY}r??$<`wAO?JQk@?RGvkWVJlq2dk9vB(yV^vm{=NVI8dhsX<)O(#nr9YD?I?(VmQ z^r7VfUBn<~p3()8yOBjm$#KWx!5hRW)5Jl7wY@ky9lNM^jaT##8QGVsYeaVywmpv>X|Xj7gWE1Ezai&wVLt3p)k4w~yrskT-!PR!kiyQlaxl(( zXhF%Q9x}1TMt3~u@|#wWm-Vq?ZerK={8@~&@9r5JW}r#45#rWii};t`{5#&3$W)|@ zbAf2yDNe0q}NEUvq_Quq3cTjcw z@H_;$hu&xllCI9CFDLuScEMg|x{S7GdV8<&Mq=ezDnRZAyX-8gv97YTm0bg=d)(>N z+B2FcqvI9>jGtnK%eO%y zoBPkJTk%y`8TLf4)IXPBn`U|9>O~WL2C~C$z~9|0m*YH<-vg2CD^SX#&)B4ngOSG$ zV^wmy_iQk>dfN@Pv(ckfy&#ak@MLC7&Q6Ro#!ezM*VEh`+b3Jt%m(^T&p&WJ2Oqvj zs-4nq0TW6cv~(YI$n0UkfwN}kg3_fp?(ijSV#tR9L0}l2qjc7W?i*q01=St0eZ=4h zyGQbEw`9OEH>NMuIe)hVwYHsGERWOD;JxEiO7cQv%pFCeR+IyhwQ|y@&^24k+|8fD zLiOWFNJ2&vu2&`Jv96_z-Cd5RLgmeY3*4rDOQo?Jm`;I_(+ejsPM03!ly!*Cu}Cco zrQSrEDHNyzT(D5s1rZq!8#?f6@v6dB7a-aWs(Qk>N?UGAo{gytlh$%_IhyL7h?DLXDGx zgxGEBQoCAWo-$LRvM=F5MTle`M})t3vVv;2j0HZY&G z22^iGhV@uaJh(XyyY%} zd4iH_UfdV#T=3n}(Lj^|n;O4|$;xhu*8T3hR1mc_A}fK}jfZ7LX~*n5+`8N2q#rI$ z@<_2VANlYF$vIH$ zl<)+*tIWW78IIINA7Rr7i{<;#^yzxoLNkXL)eSs=%|P>$YQIh+ea_3k z_s7r4%j7%&*NHSl?R4k%1>Z=M9o#zxY!n8sL5>BO-ZP;T3Gut>iLS@U%IBrX6BA3k z)&@q}V8a{X<5B}K5s(c(LQ=%v1ocr`t$EqqY0EqVjr65usa=0bkf|O#ky{j3)WBR(((L^wmyHRzoWuL2~WTC=`yZ zn%VX`L=|Ok0v7?s>IHg?yArBcync5rG#^+u)>a%qjES%dRZoIyA8gQ;StH z1Ao7{<&}6U=5}4v<)1T7t!J_CL%U}CKNs-0xWoTTeqj{5{?Be$L0_tk>M9o8 zo371}S#30rKZFM{`H_(L`EM9DGp+Mifk&IP|C2Zu_)Ghr4Qtpmkm1osCf@%Z$%t+7 zYH$Cr)Ro@3-QDeQJ8m+x6%;?YYT;k6Z0E-?kr>x33`H%*ueBD7Zx~3&HtWn0?2Wt} zTG}*|v?{$ajzt}xPzV%lL1t-URi8*Zn)YljXNGDb>;!905Td|mpa@mHjIH%VIiGx- zd@MqhpYFu4_?y5N4xiHn3vX&|e6r~Xt> zZG`aGq|yTNjv;9E+Txuoa@A(9V7g?1_T5FzRI;!=NP1Kqou1z5?%X~Wwb{trRfd>i z8&y^H)8YnKyA_Fyx>}RNmQIczT?w2J4SNvI{5J&}Wto|8FR(W;Qw#b1G<1%#tmYzQ zQ2mZA-PAdi%RQOhkHy9Ea#TPSw?WxwL@H@cbkZwIq0B!@ns}niALidmn&W?!Vd4Gj zO7FiuV4*6Mr^2xlFSvM;Cp_#r8UaqIzHJQg_z^rEJw&OMm_8NGAY2)rKvki|o1bH~ z$2IbfVeY2L(^*rMRU1lM5Y_sgrDS`Z??nR2lX;zyR=c%UyGb*%TC-Dil?SihkjrQy~TMv6;BMs7P8il`H7DmpVm@rJ;b)hW)BL)GjS154b*xq-NXq2cwE z^;VP7ua2pxvCmxrnqUYQMH%a%nHmwmI33nJM(>4LznvY*k&C0{8f*%?zggpDgkuz&JBx{9mfb@wegEl2v!=}Sq2Gaty0<)UrOT0{MZtZ~j5y&w zXlYa_jY)I_+VA-^#mEox#+G>UgvM!Ac8zI<%JRXM_73Q!#i3O|)lOP*qBeJG#BST0 zqohi)O!|$|2SeJQo(w6w7%*92S})XfnhrH_Z8qe!G5>CglP=nI7JAOW?(Z29;pXJ9 zR9`KzQ=WEhy*)WH>$;7Cdz|>*i>=##0bB)oU0OR>>N<21e4rMCHDemNi2LD>Nc$;& zQRFthpWniC1J6@Zh~iJCoLOxN`oCKD5Q4r%ynwgUKPlIEd#?QViIqovY|czyK8>6B zSP%{2-<;%;1`#0mG^B(8KbtXF;Nf>K#Di72UWE4gQ%(_26Koiad)q$xRL~?pN71ZZ zujaaCx~jXjygw;rI!WB=xrOJO6HJ!!w}7eiivtCg5K|F6$EXa)=xUC za^JXSX98W`7g-tm@uo|BKj39Dl;sg5ta;4qjo^pCh~{-HdLl6qI9Ix6f$+qiZ$}s= zNguKrU;u+T@ko(Vr1>)Q%h$?UKXCY>3se%&;h2osl2D zE4A9bd7_|^njDd)6cI*FupHpE3){4NQ*$k*cOWZ_?CZ>Z4_fl@n(mMnYK62Q1d@+I zr&O))G4hMihgBqRIAJkLdk(p(D~X{-oBUA+If@B}j& zsHbeJ3RzTq96lB7d($h$xTeZ^gP0c{t!Y0c)aQE;$FY2!mACg!GDEMKXFOPI^)nHZ z`aSPJpvV0|bbrzhWWkuPURlDeN%VT8tndV8?d)eN*i4I@u zVKl^6{?}A?P)Fsy?3oi#clf}L18t;TjNI2>eI&(ezDK7RyqFxcv%>?oxUlonv(px) z$vnPzRH`y5A(x!yOIfL0bmgeMQB$H5wenx~!ujQK*nUBW;@Em&6Xv2%s(~H5WcU2R z;%Nw<$tI)a`Ve!>x+qegJnQsN2N7HaKzrFqM>`6R*gvh%O*-%THt zrB$Nk;lE;z{s{r^PPm5qz(&lM{sO*g+W{sK+m3M_z=4=&CC>T`{X}1Vg2PEfSj2x_ zmT*(x;ov%3F?qoEeeM>dUn$a*?SIGyO8m806J1W1o+4HRhc2`9$s6hM#qAm zChQ87b~GEw{ADfs+5}FJ8+|bIlIv(jT$Ap#hSHoXdd9#w<#cA<1Rkq^*EEkknUd4& zoIWIY)sAswy6fSERVm&!SO~#iN$OgOX*{9@_BWFyJTvC%S++ilSfCrO(?u=Dc?CXZ zzCG&0yVR{Z`|ZF0eEApWEo#s9osV>F{uK{QA@BES#&;#KsScf>y zvs?vIbI>VrT<*!;XmQS=bhq%46-aambZ(8KU-wOO2=en~D}MCToB_u;Yz{)1ySrPZ z@=$}EvjTdzTWU7c0ZI6L8=yP+YRD_eMMos}b5vY^S*~VZysrkq<`cK3>>v%uy7jgq z0ilW9KjVDHLv0b<1K_`1IkbTOINs0=m-22c%M~l=^S}%hbli-3?BnNq?b`hx^HX2J zIe6ECljRL0uBWb`%{EA=%!i^4sMcj+U_TaTZRb+~GOk z^ZW!nky0n*Wb*r+Q|9H@ml@Z5gU&W`(z4-j!OzC1wOke`TRAYGZVl$PmQ16{3196( zO*?`--I}Qf(2HIwb2&1FB^!faPA2=sLg(@6P4mN)>Dc3i(B0;@O-y2;lM4akD>@^v z=u>*|!s&9zem70g7zfw9FXl1bpJW(C#5w#uy5!V?Q(U35A~$dR%LDVnq@}kQm13{} zd53q3N(s$Eu{R}k2esbftfjfOITCL;jWa$}(mmm}d(&7JZ6d3%IABCapFFYjdEjdK z&4Edqf$G^MNAtL=uCDRs&Fu@FXRgX{*0<(@c3|PNHa>L%zvxWS={L8%qw`STm+=Rd zA}FLspESSIpE_^41~#5yI2bJ=9`oc;GIL!JuW&7YetZ?0H}$$%8rW@*J37L-~Rsx!)8($nI4 zZhcZ2^=Y+p4YPl%j!nFJA|*M^gc(0o$i3nlphe+~-_m}jVkRN{spFs(o0ajW@f3K{ zDV!#BwL322CET$}Y}^0ixYj2w>&Xh12|R8&yEw|wLDvF!lZ#dOTHM9pK6@Nm-@9Lnng4ZHBgBSrr7KI8YCC9DX5Kg|`HsiwJHg2(7#nS;A{b3tVO?Z% za{m5b3rFV6EpX;=;n#wltDv1LE*|g5pQ+OY&*6qCJZc5oDS6Z6JD#6F)bWxZSF@q% z+1WV;m!lRB!n^PC>RgQCI#D1br_o^#iPk>;K2hB~0^<~)?p}LG%kigm@moD#q3PE+ zA^Qca)(xnqw6x>XFhV6ku9r$E>bWNrVH9fum0?4s?Rn2LG{Vm_+QJHse6xa%nzQ?k zKug4PW~#Gtb;#5+9!QBgyB@q=sk9=$S{4T>wjFICStOM?__fr+Kei1 z3j~xPqW;W@YkiUM;HngG!;>@AITg}vAE`M2Pj9Irl4w1fo4w<|Bu!%rh%a(Ai^Zhi zs92>v5;@Y(Zi#RI*ua*h`d_7;byQSa*v9E{2x$<-_=5Z<7{%)}4XExANcz@rK69T0x3%H<@frW>RA8^swA+^a(FxK| zFl3LD*ImHN=XDUkrRhp6RY5$rQ{bRgSO*(vEHYV)3Mo6Jy3puiLmU&g82p{qr0F?ohmbz)f2r{X2|T2 z$4fdQ=>0BeKbiVM!e-lIIs8wVTuC_m7}y4A_%ikI;Wm5$9j(^Y z(cD%U%k)X>_>9~t8;pGzL6L-fmQO@K; zo&vQzMlgY95;1BSkngY)e{`n0!NfVgf}2mB3t}D9@*N;FQ{HZ3Pb%BK6;5#-O|WI( zb6h@qTLU~AbVW#_6?c!?Dj65Now7*pU{h!1+eCV^KCuPAGs28~3k@ueL5+u|Z-7}t z9|lskE`4B7W8wMs@xJa{#bsCGDFoRSNSnmNYB&U7 zVGKWe%+kFB6kb)e;TyHfqtU6~fRg)f|>=5(N36)0+C z`hv65J<$B}WUc!wFAb^QtY31yNleq4dzmG`1wHTj=c*=hay9iD071Hc?oYoUk|M*_ zU1GihAMBsM@5rUJ(qS?9ZYJ6@{bNqJ`2Mr+5#hKf?doa?F|+^IR!8lq9)wS3tF_9n zW_?hm)G(M+MYb?V9YoX^_mu5h-LP^TL^!Q9Z7|@sO(rg_4+@=PdI)WL(B7`!K^ND- z-uIuVDCVEdH_C@c71YGYT^_Scf_dhB8Z2Xy6vGtBSlYud9vggOqv^L~F{BraSE_t} zIkP+Hp2&nH^-MNEs}^`oMLy11`PQW$T|K(`Bu*(f@)mv1-qY(_YG&J2M2<7k;;RK~ zL{Fqj9yCz8(S{}@c)S!65aF<=&eLI{hAMErCx&>i7OeDN>okvegO87OaG{Jmi<|}D zaT@b|0X{d@OIJ7zvT>r+eTzgLq~|Dpu)Z&db-P4z*`M$UL51lf>FLlq6rfG)%doyp z)3kk_YIM!03eQ8Vu_2fg{+osaEJPtJ-s36R+5_AEG12`NG)IQ#TF9c@$99%0iye+ zUzZ57=m2)$D(5Nx!n)=5Au&O0BBgwxIBaeI(mro$#&UGCr<;C{UjJVAbVi%|+WP(a zL$U@TYCxJ=1{Z~}rnW;7UVb7+ZnzgmrogDxhjLGo>c~MiJAWs&&;AGg@%U?Y^0JhL ze(x6Z74JG6FlOFK(T}SXQfhr}RIFl@QXKnIcXYF)5|V~e-}suHILKT-k|<*~Ij|VF zC;t@=uj=hot~*!C68G8hTA%8SzOfETOXQ|3FSaIEjvBJp(A)7SWUi5!Eu#yWgY+;n zlm<$+UDou*V+246_o#V4kMdto8hF%%Lki#zPh}KYXmMf?hrN0;>Mv%`@{0Qn`Ujp) z=lZe+13>^Q!9zT);H<(#bIeRWz%#*}sgUX9P|9($kexOyKIOc`dLux}c$7It4u|Rl z6SSkY*V~g_B-hMPo_ak>>z@AVQ(_N)VY2kB3IZ0G(iDUYw+2d7W^~(Jq}KY=JnWS( z#rzEa&0uNhJ>QE8iiyz;n2H|SV#Og+wEZv=f2%1ELX!SX-(d3tEj$5$1}70Mp<&eI zCkfbByL7af=qQE@5vDVxx1}FSGt_a1DoE3SDI+G)mBAna)KBG4p8Epxl9QZ4BfdAN zFnF|Y(umr;gRgG6NLQ$?ZWgllEeeq~z^ZS7L?<(~O&$5|y)Al^iMKy}&W+eMm1W z7EMU)u^ke(A1#XCV>CZ71}P}0x)4wtHO8#JRG3MA-6g=`ZM!FcICCZ{IEw8Dm2&LQ z1|r)BUG^0GzI6f946RrBlfB1Vs)~8toZf~7)+G;pv&XiUO(%5bm)pl=p>nV^o*;&T z;}@oZSibzto$arQgfkp|z4Z($P>dTXE{4O=vY0!)kDO* zGF8a4wq#VaFpLfK!iELy@?-SeRrdz%F*}hjKcA*y@mj~VD3!it9lhRhX}5YOaR9$} z3mS%$2Be7{l(+MVx3 z(4?h;P!jnRmX9J9sYN#7i=iyj_5q7n#X(!cdqI2lnr8T$IfOW<_v`eB!d9xY1P=2q&WtOXY=D9QYteP)De?S4}FK6#6Ma z=E*V+#s8>L;8aVroK^6iKo=MH{4yEZ_>N-N z`(|;aOATba1^asjxlILk<4}f~`39dBFlxj>Dw(hMYKPO3EEt1@S`1lxFNM+J@uB7T zZ8WKjz7HF1-5&2=l=fqF-*@>n5J}jIxdDwpT?oKM3s8Nr`x8JnN-kCE?~aM1H!hAE z%%w(3kHfGwMnMmNj(SU(w42OrC-euI>Dsjk&jz3ts}WHqmMpzQ3vZrsXrZ|}+MHA7 z068obeXZTsO*6RS@o3x80E4ok``rV^Y3hr&C1;|ZZ0|*EKO`$lECUYG2gVFtUTw)R z4Um<0ZzlON`zTdvVdL#KFoMFQX*a5wM0Czp%wTtfK4Sjs)P**RW&?lP$(<}q%r68Z zS53Y!d@&~ne9O)A^tNrXHhXBkj~$8j%pT1%%mypa9AW5E&s9)rjF4@O3ytH{0z6riz|@< zB~UPh*wRFg2^7EbQrHf0y?E~dHlkOxof_a?M{LqQ^C!i2dawHTPYUE=X@2(3<=OOxs8qn_(y>pU>u^}3y&df{JarR0@VJn0f+U%UiF=$Wyq zQvnVHESil@d|8&R<%}uidGh7@u^(%?$#|&J$pvFC-n8&A>utA=n3#)yMkz+qnG3wd zP7xCnF|$9Dif@N~L)Vde3hW8W!UY0BgT2v(wzp;tlLmyk2%N|0jfG$%<;A&IVrOI< z!L)o>j>;dFaqA3pL}b-Je(bB@VJ4%!JeX@3x!i{yIeIso^=n?fDX`3bU=eG7sTc%g%ye8$v8P@yKE^XD=NYxTb zbf!Mk=h|otpqjFaA-vs5YOF-*GwWPc7VbaOW&stlANnCN8iftFMMrUdYNJ_Bnn5Vt zxfz@Ah|+4&P;reZxp;MmEI7C|FOv8NKUm8njF7Wb6Gi7DeODLl&G~}G4be&*Hi0Qw z5}77vL0P+7-B%UL@3n1&JPxW^d@vVwp?u#gVcJqY9#@-3X{ok#UfW3<1fb%FT`|)V~ggq z(3AUoUS-;7)^hCjdT0Kf{i}h)mBg4qhtHHBti=~h^n^OTH5U*XMgDLIR@sre`AaB$ zg)IGBET_4??m@cx&c~bA80O7B8CHR7(LX7%HThkeC*@vi{-pL%e)yXp!B2InafbDF zjPXf1mko3h59{lT6EEbxKO1Z5GF71)WwowO6kY|6tjSVSWdQ}NsK2x{>i|MKZK8%Q zfu&_0D;CO-Jg0#YmyfctyJ!mRJp)e#@O0mYdp|8x;G1%OZQ3Q847YWTyy|%^cpA;m zze0(5p{tMu^lDkpe?HynyO?a1$_LJl2L&mpeKu%8YvgRNr=%2z${%WThHG=vrWY@4 zsA`OP#O&)TetZ>s%h!=+CE15lOOls&nvC~$Qz0Ph7tHiP;O$i|eDwpT{cp>+)0-|; zY$|bB+Gbel>5aRN3>c0x)4U=|X+z+{ zn*_p*EQoquRL+=+p;=lm`d71&1NqBz&_ph)MXu(Nv6&XE7(RsS)^MGj5Q?Fwude-(sq zjJ>aOq!7!EN>@(fK7EE#;i_BGvli`5U;r!YA{JRodLBc6-`n8K+Fjgwb%sX;j=qHQ z7&Tr!)!{HXoO<2BQrV9Sw?JRaLXV8HrsNevvnf>Y-6|{T!pYLl7jp$-nEE z#X!4G4L#K0qG_4Z;Cj6=;b|Be$hi4JvMH!-voxqx^@8cXp`B??eFBz2lLD8RRaRGh zn7kUfy!YV~p(R|p7iC1Rdgt$_24i0cd-S8HpG|`@my70g^y`gu%#Tf_L21-k?sRRZHK&at(*ED0P8iw{7?R$9~OF$Ko;Iu5)ur5<->x!m93Eb zFYpIx60s=Wxxw=`$aS-O&dCO_9?b1yKiPCQmSQb>T)963`*U+Ydj5kI(B(B?HNP8r z*bfSBpSu)w(Z3j7HQoRjUG(+d=IaE~tv}y14zHHs|0UcN52fT8V_<@2ep_ee{QgZG zmgp8iv4V{k;~8@I%M3<#B;2R>Ef(Gg_cQM7%}0s*^)SK6!Ym+~P^58*wnwV1BW@eG z4sZLqsUvBbFsr#8u7S1r4teQ;t)Y@jnn_m5jS$CsW1um!p&PqAcc8!zyiXHVta9QC zY~wCwCF0U%xiQPD_INKtTb;A|Zf29(mu9NI;E zc-e>*1%(LSXB`g}kd`#}O;veb<(sk~RWL|f3ljxCnEZDdNSTDV6#Td({6l&y4IjKF z^}lIUq*ZUqgTPumD)RrCN{M^jhY>E~1pn|KOZ5((%F)G|*ZQ|r4zIbrEiV%42hJV8 z3xS)=!X1+=olbdGJ=yZil?oXLct8FM{(6ikLL3E%=q#O6(H$p~gQu6T8N!plf!96| z&Q3=`L~>U0zZh;z(pGR2^S^{#PrPxTRHD1RQOON&f)Siaf`GLj#UOk&(|@0?zm;Sx ztsGt8=29-MZs5CSf1l1jNFtNt5rFNZxJPvkNu~2}7*9468TWm>nN9TP&^!;J{-h)_ z7WsHH9|F%I`Pb!>KAS3jQWKfGivTVkMJLO-HUGM_a4UQ_%RgL6WZvrW+Z4ujZn;y@ zz9$=oO!7qVTaQAA^BhX&ZxS*|5dj803M=k&2%QrXda`-Q#IoZL6E(g+tN!6CA!CP* zCpWtCujIea)ENl0liwVfj)Nc<9mV%+e@=d`haoZ*`B7+PNjEbXBkv=B+Pi^~L#EO$D$ZqTiD8f<5$eyb54-(=3 zh)6i8i|jp(@OnRrY5B8t|LFXFQVQ895n*P16cEKTrT*~yLH6Z4e*bZ5otpRDri&+A zfNbK1D5@O=sm`fN=WzWyse!za5n%^+6dHPGX#8DyIK>?9qyX}2XvBWVqbP%%D)7$= z=#$WulZlZR<{m#gU7lwqK4WS1Ne$#_P{b17qe$~UOXCl>5b|6WVh;5vVnR<%d+Lnp z$uEmML38}U4vaW8>shm6CzB(Wei3s#NAWE3)a2)z@i{4jTn;;aQS)O@l{rUM`J@K& l00vQ5JBs~;vo!vr%%-k{2_Fq1Mn4QF81S)AQ99zk{{c4yR+0b! diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 1af9e093..00000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100755 index 1aa94a42..00000000 --- a/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright Β© 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β», -# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»; -# * compound commands having a testable exit status, especially Β«caseΒ»; -# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 93e3f59f..00000000 --- a/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/sdk-api-gen-common/build.gradle.kts b/sdk-api-gen-common/build.gradle.kts deleted file mode 100644 index 6d69e5b9..00000000 --- a/sdk-api-gen-common/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` -} - -description = "Restate SDK API Gen Common" - -dependencies { - compileOnly(coreLibs.jspecify) - - api("com.github.jknack:handlebars:4.3.1") - api(project(":sdk-common")) -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java deleted file mode 100644 index a755d7b1..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.model; - -import java.util.Objects; - -public class Handler { - - private final CharSequence name; - private final HandlerType handlerType; - private final PayloadType inputType; - private final PayloadType outputType; - - public Handler( - CharSequence name, HandlerType handlerType, PayloadType inputType, PayloadType outputType) { - this.name = name; - this.handlerType = handlerType; - this.inputType = inputType; - this.outputType = outputType; - } - - public CharSequence getName() { - return name; - } - - public HandlerType getHandlerType() { - return handlerType; - } - - public PayloadType getInputType() { - return inputType; - } - - public PayloadType getOutputType() { - return outputType; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private CharSequence name; - private HandlerType handlerType; - private PayloadType inputType; - private PayloadType outputType; - - public Builder withName(CharSequence name) { - this.name = name; - return this; - } - - public Builder withHandlerType(HandlerType handlerType) { - this.handlerType = handlerType; - return this; - } - - public Builder withInputType(PayloadType inputType) { - this.inputType = inputType; - return this; - } - - public Builder withOutputType(PayloadType outputType) { - this.outputType = outputType; - return this; - } - - public CharSequence getName() { - return name; - } - - public HandlerType getHandlerType() { - return handlerType; - } - - public PayloadType getInputType() { - return inputType; - } - - public PayloadType getOutputType() { - return outputType; - } - - public Handler validateAndBuild() { - String handlerNameLowercase = name.toString().toLowerCase(); - if (handlerNameLowercase.startsWith("restate") - || handlerNameLowercase.startsWith("openapi")) { - throw new IllegalArgumentException( - "A service name cannot start with `restate` or `openapi`"); - } - - return new Handler( - Objects.requireNonNull(name), Objects.requireNonNull(handlerType), inputType, outputType); - } - } -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/HandlerType.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/HandlerType.java deleted file mode 100644 index af8c4288..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/HandlerType.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.model; - -public enum HandlerType { - SHARED, - EXCLUSIVE, - STATELESS, - WORKFLOW; -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/PayloadType.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/PayloadType.java deleted file mode 100644 index 136d8d9d..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/PayloadType.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.model; - -import java.util.Objects; - -public class PayloadType { - - private final boolean isEmpty; - private final String name; - private final String boxed; - private final String serdeDecl; - - public PayloadType(boolean isEmpty, String name, String boxed, String serdeDecl) { - this.isEmpty = isEmpty; - this.name = name; - this.boxed = boxed; - this.serdeDecl = serdeDecl; - } - - public boolean isEmpty() { - return isEmpty; - } - - public String getName() { - return name; - } - - public String getBoxed() { - return boxed; - } - - public String getSerdeDecl() { - return serdeDecl; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - PayloadType that = (PayloadType) o; - - if (isEmpty != that.isEmpty) return false; - if (!Objects.equals(name, that.name)) return false; - if (!Objects.equals(boxed, that.boxed)) return false; - return Objects.equals(serdeDecl, that.serdeDecl); - } - - @Override - public int hashCode() { - return Objects.hash(name, boxed, serdeDecl); - } - - @Override - public String toString() { - return "PayloadType{" - + "name='" - + name - + '\'' - + ", boxed='" - + boxed - + '\'' - + ", serdeDecl='" - + serdeDecl - + '\'' - + '}'; - } -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Service.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Service.java deleted file mode 100644 index 625b21b0..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Service.java +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.model; - -import dev.restate.sdk.common.ServiceType; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; - -public class Service { - - private final CharSequence targetPkg; - private final CharSequence targetFqcn; - private final String serviceName; - private final ServiceType serviceType; - private final List handlers; - - public Service( - CharSequence targetPkg, - CharSequence targetFqcn, - String serviceName, - ServiceType serviceType, - List handlers) { - this.targetPkg = targetPkg; - this.targetFqcn = targetFqcn; - this.serviceName = serviceName; - - this.serviceType = serviceType; - this.handlers = handlers; - } - - public CharSequence getTargetPkg() { - return this.targetPkg; - } - - public CharSequence getTargetFqcn() { - return this.targetFqcn; - } - - public String getFullyQualifiedServiceName() { - return this.serviceName; - } - - public String getSimpleServiceName() { - return this.serviceName.substring(this.serviceName.lastIndexOf('.') + 1); - } - - public CharSequence getGeneratedClassFqcnPrefix() { - if (this.targetPkg == null || this.targetPkg.length() == 0) { - return getSimpleServiceName(); - } - return this.targetPkg + "." + getSimpleServiceName(); - } - - public ServiceType getServiceType() { - return serviceType; - } - - public List getMethods() { - return handlers; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private CharSequence targetPkg; - private CharSequence targetFqcn; - private String serviceName; - private ServiceType serviceType; - private final List handlers = new ArrayList<>(); - - public Builder withTargetPkg(CharSequence targetPkg) { - this.targetPkg = targetPkg; - return this; - } - - public Builder withTargetFqcn(CharSequence targetFqcn) { - this.targetFqcn = targetFqcn; - return this; - } - - public Builder withServiceName(String serviceName) { - this.serviceName = serviceName; - return this; - } - - public Builder withServiceType(ServiceType serviceType) { - this.serviceType = serviceType; - return this; - } - - public Builder withHandlers(Collection handlers) { - this.handlers.addAll(handlers); - return this; - } - - public Builder withHandler(Handler handler) { - this.handlers.add(handler); - return this; - } - - public CharSequence getTargetPkg() { - return targetPkg; - } - - public CharSequence getTargetFqcn() { - return targetFqcn; - } - - public String getServiceName() { - return serviceName; - } - - public ServiceType getServiceType() { - return serviceType; - } - - public List getHandlers() { - return handlers; - } - - public Service validateAndBuild() { - String serviceNameLowercase = serviceName.toLowerCase(); - if (serviceNameLowercase.startsWith("restate") - || serviceNameLowercase.startsWith("openapi")) { - throw new IllegalArgumentException( - "A service name cannot start with `restate` or `openapi`"); - } - - if (serviceType.equals(ServiceType.WORKFLOW)) { - if (handlers.stream().filter(m -> m.getHandlerType().equals(HandlerType.WORKFLOW)).count() - != 1) { - throw new IllegalArgumentException( - "Workflow services must have exactly one method annotated as @Workflow"); - } - } - - return new Service( - Objects.requireNonNull(targetPkg), - Objects.requireNonNull(targetFqcn), - Objects.requireNonNull(serviceName), - Objects.requireNonNull(serviceType), - handlers); - } - } -} diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java deleted file mode 100644 index 687f68b5..00000000 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen.template; - -import com.github.jknack.handlebars.Context; -import com.github.jknack.handlebars.Handlebars; -import com.github.jknack.handlebars.Template; -import com.github.jknack.handlebars.context.FieldValueResolver; -import com.github.jknack.handlebars.helper.StringHelpers; -import com.github.jknack.handlebars.io.TemplateLoader; -import dev.restate.sdk.common.ServiceType; -import dev.restate.sdk.common.function.ThrowingFunction; -import dev.restate.sdk.gen.model.Handler; -import dev.restate.sdk.gen.model.HandlerType; -import dev.restate.sdk.gen.model.Service; -import java.io.IOException; -import java.io.Writer; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -public class HandlebarsTemplateEngine { - - private final String baseTemplateName; - private final Map templates; - private final Set handlerNamesToPrefix; - - public HandlebarsTemplateEngine( - String baseTemplateName, - TemplateLoader templateLoader, - Map templates, - Set handlerNamesToPrefix) { - this.baseTemplateName = baseTemplateName; - this.handlerNamesToPrefix = handlerNamesToPrefix; - - Handlebars handlebars = new Handlebars(templateLoader); - handlebars.registerHelpers(StringHelpers.class); - - this.templates = - templates.entrySet().stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - e -> { - try { - return handlebars.compile(e.getValue()); - } catch (IOException ex) { - throw new RuntimeException( - "Can't compile template for " - + e.getKey() - + " with base template name " - + baseTemplateName, - ex); - } - })); - } - - public void generate(ThrowingFunction createFile, Service service) - throws Throwable { - String fileName = service.getGeneratedClassFqcnPrefix() + this.baseTemplateName; - try (Writer out = createFile.apply(fileName)) { - this.templates - .get(service.getServiceType()) - .apply( - Context.newBuilder( - new ServiceTemplateModel( - service, this.baseTemplateName, this.handlerNamesToPrefix)) - .resolver(FieldValueResolver.INSTANCE) - .build(), - out); - } - } - - // --- classes to interact with the handlebars template - - static class ServiceTemplateModel { - public final String originalClassPkg; - public final String originalClassFqcn; - public final String generatedClassSimpleNamePrefix; - public final String generatedClassSimpleName; - public final String serviceName; - public final String serviceType; - public final boolean isWorkflow; - public final boolean isObject; - public final boolean isService; - public final List handlers; - - private ServiceTemplateModel( - Service inner, String baseTemplateName, Set handlerNamesToPrefix) { - this.originalClassPkg = inner.getTargetPkg().toString(); - this.originalClassFqcn = inner.getTargetFqcn().toString(); - this.generatedClassSimpleNamePrefix = inner.getSimpleServiceName(); - this.generatedClassSimpleName = this.generatedClassSimpleNamePrefix + baseTemplateName; - this.serviceName = inner.getFullyQualifiedServiceName(); - - this.serviceType = inner.getServiceType().toString(); - this.isWorkflow = inner.getServiceType() == ServiceType.WORKFLOW; - this.isObject = inner.getServiceType() == ServiceType.VIRTUAL_OBJECT; - this.isService = inner.getServiceType() == ServiceType.SERVICE; - - this.handlers = - inner.getMethods().stream() - .map(h -> new HandlerTemplateModel(h, handlerNamesToPrefix)) - .collect(Collectors.toList()); - } - } - - static class HandlerTemplateModel { - public final String name; - public final String methodName; - public final String handlerType; - public final boolean isWorkflow; - public final boolean isShared; - public final boolean isStateless; - public final boolean isExclusive; - - public final boolean inputEmpty; - public final String inputFqcn; - public final String inputSerdeDecl; - public final String boxedInputFqcn; - public final String inputSerdeFieldName; - - public final boolean outputEmpty; - public final String outputFqcn; - public final String outputSerdeDecl; - public final String boxedOutputFqcn; - public final String outputSerdeFieldName; - - private HandlerTemplateModel(Handler inner, Set handlerNamesToPrefix) { - this.name = inner.getName().toString(); - this.methodName = (handlerNamesToPrefix.contains(this.name) ? "_" : "") + this.name; - this.handlerType = inner.getHandlerType().toString(); - this.isWorkflow = inner.getHandlerType() == HandlerType.WORKFLOW; - this.isShared = inner.getHandlerType() == HandlerType.SHARED; - this.isExclusive = inner.getHandlerType() == HandlerType.EXCLUSIVE; - this.isStateless = inner.getHandlerType() == HandlerType.STATELESS; - - this.inputEmpty = inner.getInputType().isEmpty(); - this.inputFqcn = inner.getInputType().getName(); - this.inputSerdeDecl = inner.getInputType().getSerdeDecl(); - this.boxedInputFqcn = inner.getInputType().getBoxed(); - this.inputSerdeFieldName = "SERDE_" + this.name.toUpperCase() + "_INPUT"; - - this.outputEmpty = inner.getOutputType().isEmpty(); - this.outputFqcn = inner.getOutputType().getName(); - this.outputSerdeDecl = inner.getOutputType().getSerdeDecl(); - this.boxedOutputFqcn = inner.getOutputType().getBoxed(); - this.outputSerdeFieldName = "SERDE_" + this.name.toUpperCase() + "_OUTPUT"; - } - } -} diff --git a/sdk-api-gen/build.gradle.kts b/sdk-api-gen/build.gradle.kts deleted file mode 100644 index 0b43cfd5..00000000 --- a/sdk-api-gen/build.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - java - application - `library-publishing-conventions` -} - -description = "Restate SDK API Gen" - -dependencies { - compileOnly(coreLibs.jspecify) - - implementation(project(":sdk-api-gen-common")) - - implementation(project(":sdk-api")) - implementation(project(":sdk-workflow-api")) - - testAnnotationProcessor(project(":sdk-api-gen")) - testImplementation(project(":sdk-core")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(coreLibs.protobuf.java) - testImplementation(coreLibs.log4j.core) - testImplementation(platform(jacksonLibs.jackson.bom)) - testImplementation(jacksonLibs.jackson.databind) - testImplementation(project(":sdk-serde-jackson")) - - // Import test suites from sdk-core - testImplementation(project(":sdk-core", "testArchive")) -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java deleted file mode 100644 index 9b37f3b7..00000000 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java +++ /dev/null @@ -1,341 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen; - -import dev.restate.sdk.Context; -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.SharedObjectContext; -import dev.restate.sdk.annotation.Exclusive; -import dev.restate.sdk.annotation.Shared; -import dev.restate.sdk.annotation.Workflow; -import dev.restate.sdk.common.ServiceType; -import dev.restate.sdk.gen.model.*; -import dev.restate.sdk.workflow.WorkflowContext; -import dev.restate.sdk.workflow.WorkflowSharedContext; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.annotation.processing.Messager; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; - -public class ElementConverter { - - private static final PayloadType EMPTY_PAYLOAD = - new PayloadType(true, "", "Void", "dev.restate.sdk.common.CoreSerdes.VOID"); - - private final Messager messager; - private final Elements elements; - private final Types types; - - public ElementConverter(Messager messager, Elements elements, Types types) { - this.messager = messager; - this.elements = elements; - this.types = types; - } - - public Service fromTypeElement(TypeElement element) { - validateType(element); - - dev.restate.sdk.annotation.Service serviceAnnotation = - element.getAnnotation(dev.restate.sdk.annotation.Service.class); - dev.restate.sdk.annotation.VirtualObject virtualObjectAnnotation = - element.getAnnotation(dev.restate.sdk.annotation.VirtualObject.class); - dev.restate.sdk.annotation.Workflow workflowAnnotation = - element.getAnnotation(dev.restate.sdk.annotation.Workflow.class); - boolean isAnnotatedWithService = serviceAnnotation != null; - boolean isAnnotatedWithVirtualObject = virtualObjectAnnotation != null; - boolean isAnnotatedWithWorkflow = workflowAnnotation != null; - - // Should be guaranteed by the caller - assert isAnnotatedWithWorkflow || isAnnotatedWithVirtualObject || isAnnotatedWithService; - - // Check there's no more than one annotation - if (!Boolean.logicalXor( - isAnnotatedWithService, - Boolean.logicalXor(isAnnotatedWithWorkflow, isAnnotatedWithVirtualObject))) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The type can be annotated only with one annotation between @VirtualObject, @Workflow and @Service", - element); - } - - ServiceType type = - isAnnotatedWithWorkflow - ? ServiceType.WORKFLOW - : isAnnotatedWithService ? ServiceType.SERVICE : ServiceType.VIRTUAL_OBJECT; - - // Infer names - - CharSequence targetPkg = elements.getPackageOf(element).getQualifiedName(); - CharSequence targetFqcn = element.getQualifiedName(); - - String serviceName = - isAnnotatedWithService - ? serviceAnnotation.name() - : isAnnotatedWithVirtualObject - ? virtualObjectAnnotation.name() - : workflowAnnotation.name(); - if (serviceName.isEmpty()) { - // Use simple class name, flattening subclasses names - serviceName = - targetFqcn.toString().substring(targetPkg.length()).replaceAll(Pattern.quote("."), ""); - } - - // Compute handlers - List handlers = - elements.getAllMembers(element).stream() - .filter(e -> e instanceof ExecutableElement) - .filter( - e -> - e.getAnnotation(dev.restate.sdk.annotation.Handler.class) != null - || e.getAnnotation(Workflow.class) != null - || e.getAnnotation(Exclusive.class) != null - || e.getAnnotation(Shared.class) != null) - .map(e -> fromExecutableElement(type, ((ExecutableElement) e))) - .collect(Collectors.toList()); - - if (handlers.isEmpty()) { - messager.printMessage( - Diagnostic.Kind.WARNING, "The service " + serviceName + " has no handlers", element); - } - - try { - return new Service.Builder() - .withTargetPkg(targetPkg) - .withTargetFqcn(targetFqcn) - .withServiceName(serviceName) - .withServiceType(type) - .withHandlers(handlers) - .validateAndBuild(); - } catch (Exception e) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "Can't build the service " + serviceName + ": " + e.getMessage(), - element); - return null; - } - } - - private void validateType(TypeElement element) { - if (!element.getTypeParameters().isEmpty()) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The ServiceProcessor doesn't support services with generics", - element); - } - if (element.getKind().equals(ElementKind.ENUM)) { - messager.printMessage( - Diagnostic.Kind.ERROR, "The EntityProcessor doesn't support enums", element); - } - - if (element.getModifiers().contains(Modifier.PRIVATE)) { - messager.printMessage(Diagnostic.Kind.ERROR, "The annotated class is private", element); - } - } - - private Handler fromExecutableElement(ServiceType serviceType, ExecutableElement element) { - if (!element.getTypeParameters().isEmpty()) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The EntityProcessor doesn't support methods with generics", - element); - } - if (element.getKind().equals(ElementKind.CONSTRUCTOR)) { - messager.printMessage( - Diagnostic.Kind.ERROR, "You cannot annotate a constructor as Restate method"); - } - if (element.getKind().equals(ElementKind.STATIC_INIT)) { - messager.printMessage( - Diagnostic.Kind.ERROR, "You cannot annotate a static init as Restate method"); - } - - boolean isAnnotatedWithShared = element.getAnnotation(Shared.class) != null; - boolean isAnnotatedWithExclusive = element.getAnnotation(Exclusive.class) != null; - boolean isAnnotatedWithWorkflow = element.getAnnotation(Workflow.class) != null; - - // Check there's no more than one annotation - boolean hasAnyAnnotation = - isAnnotatedWithExclusive || isAnnotatedWithShared || isAnnotatedWithWorkflow; - boolean hasExactlyOneAnnotation = - Boolean.logicalXor( - isAnnotatedWithShared, - Boolean.logicalXor(isAnnotatedWithWorkflow, isAnnotatedWithExclusive)); - if (!(!hasAnyAnnotation || hasExactlyOneAnnotation)) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "You can have only one annotation between @Shared, @Exclusive and @Workflow to a method", - element); - } - - HandlerType handlerType = - isAnnotatedWithWorkflow - ? HandlerType.WORKFLOW - : isAnnotatedWithShared - ? HandlerType.SHARED - : isAnnotatedWithExclusive - ? HandlerType.EXCLUSIVE - : defaultHandlerType(serviceType, element); - - validateMethodSignature(serviceType, handlerType, element); - - try { - return new Handler.Builder() - .withName(element.getSimpleName()) - .withHandlerType(handlerType) - .withInputType( - element.getParameters().size() > 1 - ? payloadFromType(element.getParameters().get(1).asType()) - : EMPTY_PAYLOAD) - .withOutputType( - !element.getReturnType().getKind().equals(TypeKind.VOID) - ? payloadFromType(element.getReturnType()) - : EMPTY_PAYLOAD) - .validateAndBuild(); - } catch (Exception e) { - messager.printMessage( - Diagnostic.Kind.ERROR, "Error when building handler: " + e.getMessage(), element); - return null; - } - } - - private HandlerType defaultHandlerType(ServiceType serviceType, ExecutableElement element) { - switch (serviceType) { - case SERVICE: - return HandlerType.STATELESS; - case VIRTUAL_OBJECT: - return HandlerType.EXCLUSIVE; - case WORKFLOW: - messager.printMessage( - Diagnostic.Kind.ERROR, - "Workflow methods MUST be annotated with either @Shared or @Workflow", - element); - } - throw new IllegalStateException("Unexpected"); - } - - private void validateMethodSignature( - ServiceType serviceType, HandlerType handlerType, ExecutableElement element) { - switch (handlerType) { - case SHARED: - if (serviceType == ServiceType.WORKFLOW) { - validateFirstParameterType(WorkflowSharedContext.class, element); - } else if (serviceType == ServiceType.VIRTUAL_OBJECT) { - validateFirstParameterType(SharedObjectContext.class, element); - } else { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The annotation @Shared is not supported by the service type " + serviceType, - element); - } - break; - case EXCLUSIVE: - if (serviceType == ServiceType.VIRTUAL_OBJECT) { - validateFirstParameterType(ObjectContext.class, element); - } else { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The annotation @Exclusive is not supported by the service type " + serviceType, - element); - } - break; - case STATELESS: - validateFirstParameterType(Context.class, element); - break; - case WORKFLOW: - if (serviceType == ServiceType.WORKFLOW) { - validateFirstParameterType(WorkflowContext.class, element); - } else { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The annotation @Shared is not supported by the service type " + serviceType, - element); - } - break; - } - } - - private void validateFirstParameterType(Class clazz, ExecutableElement element) { - if (!types.isSameType( - element.getParameters().get(0).asType(), - elements.getTypeElement(clazz.getCanonicalName()).asType())) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "The method signature must have " + clazz.getCanonicalName() + " as first parameter", - element); - } - } - - private PayloadType payloadFromType(TypeMirror typeMirror) { - Objects.requireNonNull(typeMirror); - return new PayloadType( - false, typeMirror.toString(), boxedType(typeMirror), serdeDecl(typeMirror)); - } - - private static String serdeDecl(TypeMirror ty) { - switch (ty.getKind()) { - case BOOLEAN: - return "dev.restate.sdk.common.CoreSerdes.JSON_BOOLEAN"; - case BYTE: - return "dev.restate.sdk.common.CoreSerdes.JSON_BYTE"; - case SHORT: - return "dev.restate.sdk.common.CoreSerdes.JSON_SHORT"; - case INT: - return "dev.restate.sdk.common.CoreSerdes.JSON_INT"; - case LONG: - return "dev.restate.sdk.common.CoreSerdes.JSON_LONG"; - case CHAR: - return "dev.restate.sdk.common.CoreSerdes.JSON_CHAR"; - case FLOAT: - return "dev.restate.sdk.common.CoreSerdes.JSON_FLOAT"; - case DOUBLE: - return "dev.restate.sdk.common.CoreSerdes.JSON_DOUBLE"; - case VOID: - return "dev.restate.sdk.common.CoreSerdes.VOID"; - default: - // Default to Jackson type reference serde - return "dev.restate.sdk.serde.jackson.JacksonSerdes.of(new com.fasterxml.jackson.core.type.TypeReference<" - + ty - + ">() {})"; - } - } - - private static String boxedType(TypeMirror ty) { - switch (ty.getKind()) { - case BOOLEAN: - return "Boolean"; - case BYTE: - return "Byte"; - case SHORT: - return "Short"; - case INT: - return "Integer"; - case LONG: - return "Long"; - case CHAR: - return "Char"; - case FLOAT: - return "Float"; - case DOUBLE: - return "Double"; - case VOID: - return "Void"; - default: - return ty.toString(); - } - } -} diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/FilerTemplateLoader.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/FilerTemplateLoader.java deleted file mode 100644 index ef6707d1..00000000 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/FilerTemplateLoader.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen; - -import com.github.jknack.handlebars.io.AbstractTemplateLoader; -import com.github.jknack.handlebars.io.TemplateSource; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.nio.file.Paths; -import javax.annotation.processing.Filer; -import javax.tools.StandardLocation; - -/** - * We need this because the built-in ClassLoaderTemplateLoader is not reliable in the annotation - * processor context - */ -class FilerTemplateLoader extends AbstractTemplateLoader { - private final Filer filer; - - public FilerTemplateLoader(Filer filer) { - this.filer = filer; - } - - @Override - public TemplateSource sourceAt(String location) { - Path path = Paths.get(location); - return new TemplateSource() { - @Override - public String content(Charset charset) throws IOException { - return filer - .getResource( - StandardLocation.ANNOTATION_PROCESSOR_PATH, - path.getParent().toString().replace('/', '.'), - path.getFileName().toString()) - .getCharContent(true) - .toString(); - } - - @Override - public String filename() { - return "/" + location; - } - - @Override - public long lastModified() { - return 0; - } - }; - } -} diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java deleted file mode 100644 index f8893c76..00000000 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ServiceProcessor.java +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.gen; - -import dev.restate.sdk.common.BindableServiceFactory; -import dev.restate.sdk.common.ServiceType; -import dev.restate.sdk.common.function.ThrowingFunction; -import dev.restate.sdk.gen.model.Service; -import dev.restate.sdk.gen.template.HandlebarsTemplateEngine; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.*; -import java.util.stream.Collectors; -import javax.annotation.processing.*; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.tools.FileObject; -import javax.tools.StandardLocation; - -@SupportedAnnotationTypes({ - "dev.restate.sdk.annotation.Service", - "dev.restate.sdk.annotation.Workflow", - "dev.restate.sdk.annotation.VirtualObject" -}) -@SupportedSourceVersion(SourceVersion.RELEASE_11) -public class ServiceProcessor extends AbstractProcessor { - - private HandlebarsTemplateEngine bindableServiceFactoryCodegen; - private HandlebarsTemplateEngine bindableServiceCodegen; - private HandlebarsTemplateEngine clientCodegen; - - private static final Set RESERVED_METHOD_NAMES = Set.of("send"); - - @Override - public synchronized void init(ProcessingEnvironment processingEnv) { - super.init(processingEnv); - - FilerTemplateLoader filerTemplateLoader = new FilerTemplateLoader(processingEnv.getFiler()); - - this.bindableServiceFactoryCodegen = - new HandlebarsTemplateEngine( - "BindableServiceFactory", - filerTemplateLoader, - Map.of( - ServiceType.WORKFLOW, - "templates/BindableServiceFactory.hbs", - ServiceType.SERVICE, - "templates/BindableServiceFactory.hbs", - ServiceType.VIRTUAL_OBJECT, - "templates/BindableServiceFactory.hbs"), - RESERVED_METHOD_NAMES); - this.bindableServiceCodegen = - new HandlebarsTemplateEngine( - "BindableService", - filerTemplateLoader, - Map.of( - ServiceType.WORKFLOW, - "templates/workflow/BindableService.hbs", - ServiceType.SERVICE, - "templates/BindableService.hbs", - ServiceType.VIRTUAL_OBJECT, - "templates/BindableService.hbs"), - RESERVED_METHOD_NAMES); - this.clientCodegen = - new HandlebarsTemplateEngine( - "Client", - filerTemplateLoader, - Map.of( - ServiceType.WORKFLOW, - "templates/workflow/Client.hbs", - ServiceType.SERVICE, - "templates/Client.hbs", - ServiceType.VIRTUAL_OBJECT, - "templates/Client.hbs"), - RESERVED_METHOD_NAMES); - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - ElementConverter converter = - new ElementConverter( - processingEnv.getMessager(), - processingEnv.getElementUtils(), - processingEnv.getTypeUtils()); - - // Parsing phase - List> parsedServices = - annotations.stream() - .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream()) - .filter(e -> e.getKind().isClass() || e.getKind().isInterface()) - .map(e -> Map.entry((Element) e, converter.fromTypeElement((TypeElement) e))) - .collect(Collectors.toList()); - - Filer filer = processingEnv.getFiler(); - - // Run code generation - for (Map.Entry e : parsedServices) { - try { - ThrowingFunction fileCreator = - name -> filer.createSourceFile(name, e.getKey()).openWriter(); - this.bindableServiceFactoryCodegen.generate(fileCreator, e.getValue()); - this.bindableServiceCodegen.generate(fileCreator, e.getValue()); - this.clientCodegen.generate(fileCreator, e.getValue()); - } catch (Throwable ex) { - throw new RuntimeException(ex); - } - } - - // META-INF - Path resourceFilePath; - try { - resourceFilePath = - readOrCreateResource( - processingEnv.getFiler(), - "META-INF/services/" + BindableServiceFactory.class.getCanonicalName()); - Files.createDirectories(resourceFilePath.getParent()); - } catch (IOException e) { - throw new RuntimeException(e); - } - - try (BufferedWriter writer = - Files.newBufferedWriter( - resourceFilePath, - StandardCharsets.UTF_8, - StandardOpenOption.WRITE, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND)) { - for (Map.Entry e : parsedServices) { - writer.write(e.getValue().getGeneratedClassFqcnPrefix() + "BindableServiceFactory"); - writer.write('\n'); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - return false; - } - - public static Path readOrCreateResource(Filer filer, String file) throws IOException { - try { - FileObject fileObject = filer.getResource(StandardLocation.CLASS_OUTPUT, "", file); - return new File(fileObject.toUri()).toPath(); - } catch (IOException e) { - FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", file); - return new File(fileObject.toUri()).toPath(); - } - } -} diff --git a/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 477bc44f..00000000 --- a/sdk-api-gen/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -dev.restate.sdk.gen.ServiceProcessor \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/BindableService.hbs b/sdk-api-gen/src/main/resources/templates/BindableService.hbs deleted file mode 100644 index 132adc65..00000000 --- a/sdk-api-gen/src/main/resources/templates/BindableService.hbs +++ /dev/null @@ -1,39 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -public class {{generatedClassSimpleName}} implements dev.restate.sdk.common.BindableService { - - public static final String SERVICE_NAME = "{{serviceName}}"; - - private final dev.restate.sdk.Service service; - - public {{generatedClassSimpleName}}({{originalClassFqcn}} bindableService) { - this(bindableService, dev.restate.sdk.Service.Options.DEFAULT); - } - - public {{generatedClassSimpleName}}({{originalClassFqcn}} bindableService, dev.restate.sdk.Service.Options options) { - this.service = dev.restate.sdk.Service.{{#if isObject}}virtualObject{{else}}service{{/if}}(SERVICE_NAME) - {{#handlers}} - .{{#if isShared}}withShared{{else if isExclusive}}withExclusive{{else}}with{{/if}}( - dev.restate.sdk.Service.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), - (ctx, req) -> { - {{#if outputEmpty}} - {{#if inputEmpty}}bindableService.{{name}}(ctx){{else}}bindableService.{{name}}(ctx, req){{/if}}; - return null; - {{else}} - return {{#if inputEmpty}}bindableService.{{name}}(ctx){{else}}bindableService.{{name}}(ctx, req){{/if}}; - {{/if}} - }) - {{/handlers}} - .build(options); - } - - @Override - public dev.restate.sdk.Service.Options options() { - return this.service.options(); - } - - @Override - public java.util.List> definitions() { - return this.service.definitions(); - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/BindableServiceFactory.hbs b/sdk-api-gen/src/main/resources/templates/BindableServiceFactory.hbs deleted file mode 100644 index 17cc063a..00000000 --- a/sdk-api-gen/src/main/resources/templates/BindableServiceFactory.hbs +++ /dev/null @@ -1,16 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -import dev.restate.sdk.Service; - -public class {{generatedClassSimpleName}} implements dev.restate.sdk.common.BindableServiceFactory<{{originalClassFqcn}}, Service.Options> { - - @java.lang.Override - public dev.restate.sdk.common.BindableService create({{originalClassFqcn}} bindableService) { - return new {{generatedClassSimpleNamePrefix}}BindableService(bindableService); - } - - @java.lang.Override - public boolean supports(Object serviceObject) { - return serviceObject instanceof {{originalClassFqcn}}; - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/Client.hbs b/sdk-api-gen/src/main/resources/templates/Client.hbs deleted file mode 100644 index f58b3c65..00000000 --- a/sdk-api-gen/src/main/resources/templates/Client.hbs +++ /dev/null @@ -1,167 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -import dev.restate.sdk.Awaitable; -import dev.restate.sdk.Context; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.Target; -import java.util.Optional; -import java.time.Duration; - -public class {{generatedClassSimpleName}} { - - public static final String SERVICE_NAME = "{{serviceName}}"; - - {{#handlers}} - private static final Serde<{{{boxedInputFqcn}}}> {{inputSerdeFieldName}} = {{{inputSerdeDecl}}}; - private static final Serde<{{{boxedOutputFqcn}}}> {{outputSerdeFieldName}} = {{{outputSerdeDecl}}}; - {{/handlers}} - - public static ContextClient fromContext(Context ctx{{#isObject}}, String key{{/isObject}}) { - return new ContextClient(ctx{{#isObject}}, key{{/isObject}}); - } - - public static IngressClient fromIngress(dev.restate.sdk.client.IngressClient ingressClient{{#isObject}}, String key{{/isObject}}) { - return new IngressClient(ingressClient{{#isObject}}, key{{/isObject}}); - } - - public static IngressClient fromIngress(String baseUri{{#isObject}}, String key{{/isObject}}) { - return new IngressClient(dev.restate.sdk.client.IngressClient.defaultClient(baseUri){{#isObject}}, key{{/isObject}}); - } - - public static class ContextClient { - - private final Context ctx; - {{#isObject}}private final String key;{{/isObject}} - - public ContextClient(Context ctx{{#isObject}}, String key{{/isObject}}) { - this.ctx = ctx; - {{#isObject}}this.key = key;{{/isObject}} - } - - {{#handlers}} - public Awaitable<{{{boxedOutputFqcn}}}> {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return this.ctx.call( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, this.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/handlers}} - - public Send send() { - return new Send(null); - } - - public Send send(Duration delay) { - return new Send(delay); - } - - public class Send { - - private final Duration delay; - - Send(Duration delay) { - this.delay = delay; - } - - {{#handlers}} - public void {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - ContextClient.this.ctx.send( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, ContextClient.this.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}, - delay); - }{{/handlers}} - } - } - - public static class IngressClient { - - private final dev.restate.sdk.client.IngressClient ingressClient; - {{#isObject}}private final String key;{{/isObject}} - - public IngressClient(dev.restate.sdk.client.IngressClient ingressClient{{#isObject}}, String key{{/isObject}}) { - this.ingressClient = ingressClient; - {{#isObject}}this.key = key;{{/isObject}} - } - - {{#handlers}} - public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - {{^outputEmpty}}return {{/outputEmpty}}this.{{methodName}}( - {{^inputEmpty}}req, {{/inputEmpty}} - dev.restate.sdk.client.RequestOptions.DEFAULT); - } - - public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { - {{^outputEmpty}}return {{/outputEmpty}}this.ingressClient.call( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, this.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}, - requestOptions); - } - - public {{#if outputEmpty}}java.util.concurrent.CompletableFuture{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return this.{{methodName}}Async( - {{^inputEmpty}}req, {{/inputEmpty}} - dev.restate.sdk.client.RequestOptions.DEFAULT); - } - - public {{#if outputEmpty}}java.util.concurrent.CompletableFuture{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { - return this.ingressClient.callAsync( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, this.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}, - requestOptions); - }{{/handlers}} - - public Send send() { - return new Send(null); - } - - public Send send(Duration delay) { - return new Send(delay); - } - - public class Send { - - private final Duration delay; - - Send(Duration delay) { - this.delay = delay; - } - - {{#handlers}} - public String {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return this.{{methodName}}( - {{^inputEmpty}}req, {{/inputEmpty}} - dev.restate.sdk.client.RequestOptions.DEFAULT); - } - - public String {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { - return IngressClient.this.ingressClient.send( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, IngressClient.this.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}, - this.delay, - requestOptions); - } - - public java.util.concurrent.CompletableFuture {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return this.{{methodName}}Async( - {{^inputEmpty}}req, {{/inputEmpty}} - dev.restate.sdk.client.RequestOptions.DEFAULT); - } - - public java.util.concurrent.CompletableFuture {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { - return IngressClient.this.ingressClient.sendAsync( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, IngressClient.this.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}null{{else}}req{{/if}}, - this.delay, - requestOptions); - }{{/handlers}} - } - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/workflow/BindableService.hbs b/sdk-api-gen/src/main/resources/templates/workflow/BindableService.hbs deleted file mode 100644 index 0b113786..00000000 --- a/sdk-api-gen/src/main/resources/templates/workflow/BindableService.hbs +++ /dev/null @@ -1,51 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -public class {{generatedClassSimpleName}} implements dev.restate.sdk.common.BindableService { - - public static final String SERVICE_NAME = "{{serviceName}}"; - - private final dev.restate.sdk.common.BindableService service; - - public {{generatedClassSimpleName}}({{originalClassFqcn}} bindableService) { - this(bindableService, dev.restate.sdk.Service.Options.DEFAULT); - } - - public {{generatedClassSimpleName}}({{originalClassFqcn}} bindableService, dev.restate.sdk.Service.Options options) { - this.service = dev.restate.sdk.workflow.WorkflowBuilder.named( - SERVICE_NAME, - {{#handlers}}{{#if isWorkflow}} - dev.restate.sdk.Service.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), - (ctx, req) -> { - {{#if outputEmpty}} - {{#if inputEmpty}}bindableService.{{name}}(ctx){{else}}bindableService.{{name}}(ctx, req){{/if}}; - return null; - {{else}} - return {{#if inputEmpty}}bindableService.{{name}}(ctx){{else}}bindableService.{{name}}(ctx, req){{/if}}; - {{/if}} - } - {{/if}}{{/handlers}}) - {{#handlers}}{{#if isShared}} - .with{{capitalizeFirst (lower handlerType)}}( - dev.restate.sdk.Service.HandlerSignature.of("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}}), - (ctx, req) -> { - {{#if outputEmpty}} - {{#if inputEmpty}}bindableService.{{name}}(ctx){{else}}bindableService.{{name}}(ctx, req){{/if}}; - return null; - {{else}} - return {{#if inputEmpty}}bindableService.{{name}}(ctx){{else}}bindableService.{{name}}(ctx, req){{/if}}; - {{/if}} - }) - {{/if}}{{/handlers}} - .build(options); - } - - @Override - public dev.restate.sdk.Service.Options options() { - return this.service.options(); - } - - @Override - public java.util.List> definitions() { - return this.service.definitions(); - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs b/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs deleted file mode 100644 index e2afe18f..00000000 --- a/sdk-api-gen/src/main/resources/templates/workflow/Client.hbs +++ /dev/null @@ -1,148 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -import dev.restate.sdk.Awaitable; -import dev.restate.sdk.Context; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.workflow.impl.WorkflowCodegenUtil; -import java.util.Optional; -import java.time.Duration; - -public class {{generatedClassSimpleName}} { - - public static final String WORKFLOW_NAME = "{{serviceName}}"; - - {{#handlers}} - private static final Serde<{{{boxedInputFqcn}}}> {{inputSerdeFieldName}} = {{{inputSerdeDecl}}}; - private static final Serde<{{{boxedOutputFqcn}}}> {{outputSerdeFieldName}} = {{{outputSerdeDecl}}}; - {{/handlers}} - - public static ContextClient fromContext(Context ctx, String key) { - return new ContextClient(ctx, key); - } - - public static IngressClient fromIngress(dev.restate.sdk.client.IngressClient ingressClient, String key) { - return new IngressClient(ingressClient, key); - } - - public static IngressClient fromIngress(String baseUri, String key) { - return new IngressClient(dev.restate.sdk.client.IngressClient.defaultClient(baseUri), key); - } - - public static class ContextClient { - - private final Context ctx; - private final String workflowKey; - - public ContextClient(Context ctx, String workflowKey) { - this.ctx = ctx; - this.workflowKey = workflowKey; - } - - {{#handlers}}{{#if isWorkflow}} - public Awaitable submit({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return WorkflowCodegenUtil.RestateClient.submit(ctx, WORKFLOW_NAME, workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}); - } - - public Awaitable isCompleted() { - return WorkflowCodegenUtil.RestateClient.isCompleted(ctx, WORKFLOW_NAME, workflowKey); - } - - {{^outputEmpty}} - public Awaitable> getOutput() { - return WorkflowCodegenUtil.RestateClient.getOutput(ctx, WORKFLOW_NAME, workflowKey, {{outputSerdeFieldName}}); - }{{/outputEmpty}} - {{/if}}{{/handlers}} - - public Awaitable> getState(StateKey key) { - return WorkflowCodegenUtil.RestateClient.getState(ctx, WORKFLOW_NAME, workflowKey, key); - } - - {{#handlers}}{{#if isShared}} - public Awaitable<{{{boxedOutputFqcn}}}> {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return WorkflowCodegenUtil.RestateClient.invokeShared(ctx, WORKFLOW_NAME, "{{name}}", workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}, {{outputSerdeFieldName}}); - } - {{/if}}{{/handlers}} - - public Send send() { - return new Send(); - } - - public SendDelayed sendDelayed(Duration delay) { - return new SendDelayed(delay); - } - - public class Send { - - {{#handlers}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.RestateClient.invokeSharedSend(ContextClient.this.ctx, WORKFLOW_NAME, "{{name}}", ContextClient.this.workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}); - }{{/if}}{{/handlers}} - - } - - public class SendDelayed { - - private final Duration delay; - - SendDelayed(Duration delay) { - this.delay = delay; - } - - {{#handlers}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.RestateClient.invokeSharedSendDelayed(ContextClient.this.ctx, WORKFLOW_NAME, "{{name}}", ContextClient.this.workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}, delay); - }{{/if}}{{/handlers}} - } - } - - public static class IngressClient { - - private final dev.restate.sdk.client.IngressClient ingressClient; - private final String workflowKey; - - public IngressClient(dev.restate.sdk.client.IngressClient ingressClient, String workflowKey) { - this.ingressClient = ingressClient; - this.workflowKey = workflowKey; - } - - {{#handlers}}{{#if isWorkflow}} - public dev.restate.sdk.workflow.WorkflowExecutionState submit({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return WorkflowCodegenUtil.ExternalClient.submit(ingressClient, WORKFLOW_NAME, workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}); - } - - public boolean isCompleted() { - return WorkflowCodegenUtil.ExternalClient.isCompleted(ingressClient, WORKFLOW_NAME, workflowKey); - } - - {{^outputEmpty}} - public Optional<{{{outputFqcn}}}> getOutput() { - return WorkflowCodegenUtil.ExternalClient.getOutput(ingressClient, WORKFLOW_NAME, workflowKey, {{outputSerdeFieldName}}); - }{{/outputEmpty}} - {{/if}}{{/handlers}} - - public Optional getState(StateKey key) { - return WorkflowCodegenUtil.ExternalClient.getState(ingressClient, WORKFLOW_NAME, workflowKey, key); - } - - {{#handlers}}{{#if isShared}} - public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - {{^outputEmpty}}return {{/outputEmpty}}WorkflowCodegenUtil.ExternalClient.invokeShared(IngressClient.this.ingressClient, WORKFLOW_NAME, "{{name}}", IngressClient.this.workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}, {{outputSerdeFieldName}}); - } - {{/if}}{{/handlers}} - - public Send send() { - return new Send(); - } - - public class Send { - - {{#handlers}}{{#if isShared}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - WorkflowCodegenUtil.ExternalClient.invokeSharedSend(IngressClient.this.ingressClient, WORKFLOW_NAME, "{{name}}", IngressClient.this.workflowKey, {{#if inputEmpty}}null{{else}}req{{/if}}); - } - {{/if}}{{/handlers}} - - } - } -} \ No newline at end of file diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java b/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java deleted file mode 100644 index eeae2221..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.core.ProtoUtils.*; -import static dev.restate.sdk.core.TestDefinitions.testInvocation; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.annotation.*; -import dev.restate.sdk.annotation.Service; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Target; -import dev.restate.sdk.core.ProtoUtils; -import dev.restate.sdk.core.TestDefinitions; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import java.util.stream.Stream; - -public class CodegenTest implements TestSuite { - - @Service - static class ServiceGreeter { - @Handler - String greet(Context context, String request) { - return request; - } - } - - @VirtualObject - static class ObjectGreeter { - @Exclusive - String greet(ObjectContext context, String request) { - return request; - } - - @Handler - @Shared - String sharedGreet(SharedObjectContext context, String request) { - return request; - } - } - - @VirtualObject - public interface GreeterInterface { - @Exclusive - String greet(ObjectContext context, String request); - } - - private static class ObjectGreeterImplementedFromInterface implements GreeterInterface { - - @Override - public String greet(ObjectContext context, String request) { - return request; - } - } - - @Service(name = "Empty") - static class Empty { - - @Handler - public String emptyInput(Context context) { - var client = EmptyClient.fromContext(context); - return client.emptyInput().await(); - } - - @Handler - public void emptyOutput(Context context, String request) { - var client = EmptyClient.fromContext(context); - client.emptyOutput(request).await(); - } - - @Handler - public void emptyInputOutput(Context context) { - var client = EmptyClient.fromContext(context); - client.emptyInputOutput().await(); - } - } - - @Service(name = "PrimitiveTypes") - static class PrimitiveTypes { - - @Handler - public int primitiveOutput(Context context) { - var client = PrimitiveTypesClient.fromContext(context); - return client.primitiveOutput().await(); - } - - @Handler - public void primitiveInput(Context context, int input) { - var client = PrimitiveTypesClient.fromContext(context); - client.primitiveInput(input).await(); - } - } - - @VirtualObject - static class CornerCases { - @Exclusive - public String send(ObjectContext context, String request) { - // Just needs to compile - return CodegenTestCornerCasesClient.fromContext(context, request)._send("my_send").await(); - } - } - - @Override - public Stream definitions() { - return Stream.of( - testInvocation(ServiceGreeter::new, "greet") - .withInput(startMessage(1), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation(ObjectGreeter::new, "greet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation(ObjectGreeter::new, "sharedGreet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation(ObjectGreeterImplementedFromInterface::new, "greet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation(Empty::new, "emptyInput") - .withInput(startMessage(1), inputMessage(), completionMessage(1, "Till")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyInput")), - outputMessage("Till"), - END_MESSAGE) - .named("empty output"), - testInvocation(Empty::new, "emptyOutput") - .withInput( - startMessage(1), - inputMessage("Francesco"), - completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyOutput"), "Francesco"), - ProtoUtils.outputMessage(), - END_MESSAGE) - .named("empty output"), - testInvocation(Empty::new, "emptyInputOutput") - .withInput( - startMessage(1), - inputMessage("Francesco"), - completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyInputOutput")), - ProtoUtils.outputMessage(), - END_MESSAGE) - .named("empty input and empty output"), - testInvocation(PrimitiveTypes::new, "primitiveOutput") - .withInput( - startMessage(1), inputMessage(), completionMessage(1, CoreSerdes.JSON_INT, 10)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage( - Target.service("PrimitiveTypes", "primitiveOutput"), CoreSerdes.VOID, null), - outputMessage(CoreSerdes.JSON_INT, 10), - END_MESSAGE) - .named("primitive output"), - testInvocation(PrimitiveTypes::new, "primitiveInput") - .withInput( - startMessage(1), inputMessage(10), completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage( - Target.service("PrimitiveTypes", "primitiveInput"), CoreSerdes.JSON_INT, 10), - outputMessage(), - END_MESSAGE) - .named("primitive input")); - } -} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithExplicitName.java b/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithExplicitName.java deleted file mode 100644 index b3fda9b2..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithExplicitName.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; - -@Service(name = "MyExplicitName") -public interface GreeterWithExplicitName { - @Handler - String greet(Context context, String request); -} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithoutExplicitName.java b/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithoutExplicitName.java deleted file mode 100644 index 56d6b9c0..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/GreeterWithoutExplicitName.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.Service; - -@Service -public interface GreeterWithoutExplicitName { - @Handler - String greet(Context context, String request); -} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java b/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java deleted file mode 100644 index 039c56d2..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/JavaCodegenTests.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.core.MockMultiThreaded; -import dev.restate.sdk.core.MockSingleThread; -import dev.restate.sdk.core.TestDefinitions.TestExecutor; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import dev.restate.sdk.core.TestRunner; -import java.util.stream.Stream; - -public class JavaCodegenTests extends TestRunner { - - @Override - protected Stream executors() { - return Stream.of(MockSingleThread.INSTANCE, MockMultiThreaded.INSTANCE); - } - - @Override - public Stream definitions() { - return Stream.of(new CodegenTest()); - } -} diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/NameInferenceTest.java b/sdk-api-gen/src/test/java/dev/restate/sdk/NameInferenceTest.java deleted file mode 100644 index a8b49955..00000000 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/NameInferenceTest.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; - -public class NameInferenceTest { - - @Test - void expectedName() { - assertThat(CodegenTestServiceGreeterClient.SERVICE_NAME).isEqualTo("CodegenTestServiceGreeter"); - assertThat(GreeterWithoutExplicitNameClient.SERVICE_NAME) - .isEqualTo("GreeterWithoutExplicitName"); - assertThat(MyExplicitNameClient.SERVICE_NAME).isEqualTo("MyExplicitName"); - } -} diff --git a/sdk-api-kotlin-gen/build.gradle.kts b/sdk-api-kotlin-gen/build.gradle.kts deleted file mode 100644 index 94b3f3a3..00000000 --- a/sdk-api-kotlin-gen/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -plugins { - java - kotlin("jvm") - `library-publishing-conventions` - alias(kotlinLibs.plugins.ksp) -} - -description = "Restate SDK API Kotlin Gen" - -dependencies { - compileOnly(coreLibs.jspecify) - - implementation(kotlinLibs.symbol.processing.api) - implementation(project(":sdk-api-gen-common")) - - implementation(project(":sdk-api-kotlin")) - - kspTest(project(":sdk-api-kotlin-gen")) - testImplementation(project(":sdk-core")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(coreLibs.protobuf.java) - testImplementation(coreLibs.log4j.core) - testImplementation(kotlinLibs.kotlinx.coroutines) - - // Import test suites from sdk-core - testImplementation(project(":sdk-core", "testArchive")) -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt deleted file mode 100644 index 5c335f23..00000000 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin.gen - -import com.google.devtools.ksp.KspExperimental -import com.google.devtools.ksp.getAnnotationsByType -import com.google.devtools.ksp.getVisibility -import com.google.devtools.ksp.isAnnotationPresent -import com.google.devtools.ksp.processing.KSBuiltIns -import com.google.devtools.ksp.processing.KSPLogger -import com.google.devtools.ksp.symbol.* -import com.google.devtools.ksp.visitor.KSDefaultVisitor -import dev.restate.sdk.common.ServiceType -import dev.restate.sdk.gen.model.Handler -import dev.restate.sdk.gen.model.HandlerType -import dev.restate.sdk.gen.model.PayloadType -import dev.restate.sdk.gen.model.Service -import dev.restate.sdk.kotlin.Context -import dev.restate.sdk.kotlin.ObjectContext -import dev.restate.sdk.kotlin.SharedObjectContext -import java.util.regex.Pattern -import kotlin.reflect.KClass - -class KElementConverter(private val logger: KSPLogger, private val builtIns: KSBuiltIns) : - KSDefaultVisitor() { - companion object { - private val SUPPORTED_CLASS_KIND: Set = setOf(ClassKind.CLASS, ClassKind.INTERFACE) - private val EMPTY_PAYLOAD: PayloadType = - PayloadType(true, "", "Unit", "dev.restate.sdk.kotlin.KtSerdes.UNIT") - } - - override fun defaultHandler(node: KSNode, data: Service.Builder) {} - - override fun visitAnnotated(annotated: KSAnnotated, data: Service.Builder) { - if (annotated !is KSClassDeclaration) { - logger.error( - "Only classes or interfaces can be annotated with @Service or @VirtualObject or @Workflow") - } - visitClassDeclaration(annotated as KSClassDeclaration, data) - } - - @OptIn(KspExperimental::class) - override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Service.Builder) { - // Validate class declaration - if (classDeclaration.typeParameters.isNotEmpty()) { - logger.error("The ServiceProcessor doesn't support services with generics", classDeclaration) - } - if (!SUPPORTED_CLASS_KIND.contains(classDeclaration.classKind)) { - logger.error( - "The ServiceProcessor supports only class declarations of kind $SUPPORTED_CLASS_KIND", - classDeclaration) - } - if (classDeclaration.getVisibility() == Visibility.PRIVATE) { - logger.error("The annotated class is private", classDeclaration) - } - if (classDeclaration.isAnnotationPresent(dev.restate.sdk.annotation.Workflow::class)) { - logger.error("sdk-api-kotlin doesn't support workflows yet", classDeclaration) - } - - // Figure out service type annotations - val serviceAnnotation = - classDeclaration - .getAnnotationsByType(dev.restate.sdk.annotation.Service::class) - .firstOrNull() - val virtualObjectAnnotation = - classDeclaration - .getAnnotationsByType(dev.restate.sdk.annotation.VirtualObject::class) - .firstOrNull() - val isAnnotatedWithService = serviceAnnotation != null - val isAnnotatedWithVirtualObject = virtualObjectAnnotation != null - - // Check there's exactly one annotation - if (!(isAnnotatedWithService xor isAnnotatedWithVirtualObject)) { - logger.error( - "The type can be annotated only with one annotation between @VirtualObject and @Service", - classDeclaration) - } - - data.withServiceType( - if (isAnnotatedWithService) ServiceType.SERVICE else ServiceType.VIRTUAL_OBJECT) - - // Infer names - val targetPkg = classDeclaration.packageName.asString() - val targetFqcn = classDeclaration.qualifiedName!!.asString() - var serviceName = - if (isAnnotatedWithService) serviceAnnotation!!.name else virtualObjectAnnotation!!.name - if (serviceName.isEmpty()) { - // Use Simple class name - // With this logic we make sure we flatten subclasses names - serviceName = targetFqcn.substring(targetPkg.length).replace(Pattern.quote(".").toRegex(), "") - } - data.withTargetPkg(targetPkg).withTargetFqcn(targetFqcn).withServiceName(serviceName) - - // Compute handlers - classDeclaration - .getAllFunctions() - .filter { - it.isAnnotationPresent(dev.restate.sdk.annotation.Handler::class) || - it.isAnnotationPresent(dev.restate.sdk.annotation.Workflow::class) || - it.isAnnotationPresent(dev.restate.sdk.annotation.Exclusive::class) || - it.isAnnotationPresent(dev.restate.sdk.annotation.Shared::class) - } - .forEach { visitFunctionDeclaration(it, data) } - - if (data.handlers.isEmpty()) { - logger.warn( - "The class declaration $targetFqcn has no methods annotated as handlers", - classDeclaration) - } - } - - @OptIn(KspExperimental::class) - override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Service.Builder) { - // Validate function declaration - if (function.typeParameters.isNotEmpty()) { - logger.error("The ServiceProcessor doesn't support methods with generics", function) - } - if (function.functionKind != FunctionKind.MEMBER) { - logger.error("Only member function declarations are supported as Restate handlers") - } - if (function.isAnnotationPresent(dev.restate.sdk.annotation.Workflow::class)) { - logger.error("sdk-api-kotlin doesn't support workflows yet", function) - } - - val isAnnotatedWithShared = - function.isAnnotationPresent(dev.restate.sdk.annotation.Shared::class) - val isAnnotatedWithExclusive = - function.isAnnotationPresent(dev.restate.sdk.annotation.Exclusive::class) - - // Check there's no more than one annotation - val hasAnyAnnotation = isAnnotatedWithExclusive || isAnnotatedWithShared - val hasExactlyOneAnnotation = isAnnotatedWithShared xor isAnnotatedWithExclusive - if (!(!hasAnyAnnotation || hasExactlyOneAnnotation)) { - logger.error( - "You can have only one annotation between @Shared and @Exclusive to a method", function) - } - - val handlerBuilder = Handler.builder() - - // Set handler type - val handlerType = - if (isAnnotatedWithShared) HandlerType.SHARED - else if (isAnnotatedWithExclusive) HandlerType.EXCLUSIVE - else defaultHandlerType(data.serviceType, function) - handlerBuilder.withHandlerType(handlerType) - - validateMethodSignature(data.serviceType, handlerType, function) - - try { - data.withHandler( - handlerBuilder - .withName(function.simpleName.asString()) - .withHandlerType(handlerType) - .withInputType( - if (function.parameters.size == 2) payloadFromType(function.parameters[1].type) - else EMPTY_PAYLOAD) - .withOutputType( - if (function.returnType != null) payloadFromType(function.returnType!!) - else EMPTY_PAYLOAD) - .validateAndBuild()) - } catch (e: Exception) { - logger.error("Error when building handler: $e", function) - } - } - - private fun defaultHandlerType(serviceType: ServiceType, node: KSNode): HandlerType { - when (serviceType) { - ServiceType.SERVICE -> return HandlerType.STATELESS - ServiceType.VIRTUAL_OBJECT -> return HandlerType.EXCLUSIVE - ServiceType.WORKFLOW -> - logger.error("Workflow handlers MUST be annotated with either @Shared or @Workflow", node) - } - throw IllegalStateException("Unexpected") - } - - private fun validateMethodSignature( - serviceType: ServiceType, - handlerType: HandlerType, - function: KSFunctionDeclaration - ) { - if (function.parameters.isEmpty()) { - logger.error( - "The annotated method has no parameters. There must be at least the context parameter as first parameter", - function) - } - when (handlerType) { - HandlerType.SHARED -> - if (serviceType == ServiceType.VIRTUAL_OBJECT) { - validateFirstParameterType(SharedObjectContext::class, function) - } else { - logger.error( - "The annotation @Shared is not supported by the service type $serviceType", - function) - } - HandlerType.EXCLUSIVE -> - if (serviceType == ServiceType.VIRTUAL_OBJECT) { - validateFirstParameterType(ObjectContext::class, function) - } else { - logger.error( - "The annotation @Exclusive is not supported by the service type $serviceType", - function) - } - HandlerType.STATELESS -> validateFirstParameterType(Context::class, function) - HandlerType.WORKFLOW -> - logger.error( - "The annotation @Workflow is currently not supported in sdk-api-kotlin", function) - } - } - - private fun validateFirstParameterType(clazz: KClass<*>, function: KSFunctionDeclaration) { - if (function.parameters[0].type.resolve().declaration.qualifiedName!!.asString() != - clazz.qualifiedName) { - logger.error( - "The method signature must have ${clazz.qualifiedName} as first parameter, was ${function.parameters[0].type.resolve().declaration.qualifiedName!!.asString()}", - function) - } - } - - private fun payloadFromType(typeRef: KSTypeReference): PayloadType { - val ty = typeRef.resolve() - return PayloadType(false, typeRef.toString(), boxedType(ty), serdeDecl(ty)) - } - - private fun serdeDecl(ty: KSType): String { - return when (ty) { - builtIns.unitType -> "dev.restate.sdk.kotlin.KtSerdes.UNIT" - else -> "dev.restate.sdk.kotlin.KtSerdes.json<${boxedType(ty)}>()" - } - } - - private fun boxedType(ty: KSType): String { - return when (ty) { - builtIns.unitType -> "Unit" - else -> ty.toString() - } - } -} diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt deleted file mode 100644 index 12ce57f0..00000000 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessor.kt +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin.gen - -import com.github.jknack.handlebars.io.ClassPathTemplateLoader -import com.google.devtools.ksp.containingFile -import com.google.devtools.ksp.processing.* -import com.google.devtools.ksp.symbol.KSAnnotated -import com.google.devtools.ksp.symbol.Origin -import dev.restate.sdk.common.BindableServiceFactory -import dev.restate.sdk.common.ServiceType -import dev.restate.sdk.gen.model.Service -import dev.restate.sdk.gen.template.HandlebarsTemplateEngine -import java.io.BufferedWriter -import java.io.IOException -import java.io.Writer -import java.nio.charset.Charset - -class ServiceProcessor(private val logger: KSPLogger, private val codeGenerator: CodeGenerator) : - SymbolProcessor { - - companion object { - private val RESERVED_METHOD_NAMES: Set = setOf("send") - } - - private val bindableServiceFactoryCodegen: HandlebarsTemplateEngine = - HandlebarsTemplateEngine( - "BindableServiceFactory", - ClassPathTemplateLoader(), - mapOf( - ServiceType.SERVICE to "templates/BindableServiceFactory", - ServiceType.VIRTUAL_OBJECT to "templates/BindableServiceFactory"), - RESERVED_METHOD_NAMES) - private val bindableServiceCodegen: HandlebarsTemplateEngine = - HandlebarsTemplateEngine( - "BindableService", - ClassPathTemplateLoader(), - mapOf( - ServiceType.SERVICE to "templates/BindableService", - ServiceType.VIRTUAL_OBJECT to "templates/BindableService"), - RESERVED_METHOD_NAMES) - private val clientCodegen: HandlebarsTemplateEngine = - HandlebarsTemplateEngine( - "Client", - ClassPathTemplateLoader(), - mapOf( - ServiceType.SERVICE to "templates/Client", - ServiceType.VIRTUAL_OBJECT to "templates/Client"), - RESERVED_METHOD_NAMES) - - override fun process(resolver: Resolver): List { - val converter = KElementConverter(logger, resolver.builtIns) - - val resolved = - resolver - .getSymbolsWithAnnotation(dev.restate.sdk.annotation.Service::class.qualifiedName!!) - .toSet() + - resolver - .getSymbolsWithAnnotation( - dev.restate.sdk.annotation.VirtualObject::class.qualifiedName!!) - .toSet() + - resolver - .getSymbolsWithAnnotation( - dev.restate.sdk.annotation.Workflow::class.qualifiedName!!) - .toSet() - - val services = - resolved - .filter { it.containingFile!!.origin == Origin.KOTLIN } - .map { - val serviceBuilder = Service.builder() - converter.visitAnnotated(it, serviceBuilder) - - var serviceModel: Service? = null - try { - serviceModel = serviceBuilder.validateAndBuild() - } catch (e: Exception) { - logger.error("Unable to build service: $e", it) - } - (it to serviceModel!!) - } - .toList() - - // Run code generation - for (service in services) { - try { - val fileCreator: (String) -> Writer = { name: String -> - codeGenerator - .createNewFile( - Dependencies(false, service.first.containingFile!!), - service.second.targetPkg.toString(), - name) - .writer(Charset.defaultCharset()) - } - this.bindableServiceFactoryCodegen.generate(fileCreator, service.second) - this.bindableServiceCodegen.generate(fileCreator, service.second) - this.clientCodegen.generate(fileCreator, service.second) - } catch (ex: Throwable) { - throw RuntimeException(ex) - } - } - - // META-INF - if (services.isNotEmpty()) { - generateMetaINF(services) - } - - return emptyList() - } - - private fun generateMetaINF(services: List>) { - val resourceFile = "META-INF/services/${BindableServiceFactory::class.java.canonicalName}" - val dependencies = - Dependencies(true, *(services.map { it.first.containingFile!! }.toTypedArray())) - - val writer: BufferedWriter = - try { - codeGenerator.createNewFileByPath(dependencies, resourceFile, "").bufferedWriter() - } catch (e: FileSystemException) { - val existingFile = e.file - val currentValues = existingFile.readText() - val newWriter = e.file.bufferedWriter() - newWriter.write(currentValues) - newWriter - } - - try { - writer.use { - for (service in services) { - it.write("${service.second.generatedClassFqcnPrefix}BindableServiceFactory") - it.newLine() - } - } - } catch (e: IOException) { - logger.error("Unable to create $resourceFile: $e") - } - } -} diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessorProvider.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessorProvider.kt deleted file mode 100644 index c058fa08..00000000 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ServiceProcessorProvider.kt +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin.gen - -import com.google.devtools.ksp.processing.SymbolProcessor -import com.google.devtools.ksp.processing.SymbolProcessorEnvironment -import com.google.devtools.ksp.processing.SymbolProcessorProvider - -class ServiceProcessorProvider : SymbolProcessorProvider { - - override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { - return ServiceProcessor(logger = environment.logger, codeGenerator = environment.codeGenerator) - } -} diff --git a/sdk-api-kotlin-gen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/sdk-api-kotlin-gen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider deleted file mode 100644 index 034b8dc5..00000000 --- a/sdk-api-kotlin-gen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider +++ /dev/null @@ -1 +0,0 @@ -dev.restate.sdk.kotlin.gen.ServiceProcessorProvider \ No newline at end of file diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/BindableService.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/BindableService.hbs deleted file mode 100644 index bdbb106d..00000000 --- a/sdk-api-kotlin-gen/src/main/resources/templates/BindableService.hbs +++ /dev/null @@ -1,27 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}}{{/if}} - -class {{generatedClassSimpleName}}( - bindableService: {{originalClassFqcn}}, - options: dev.restate.sdk.kotlin.Service.Options = dev.restate.sdk.kotlin.Service.Options.DEFAULT -): dev.restate.sdk.common.BindableService { - - companion object { - const val SERVICE_NAME: String = "{{serviceName}}"; - } - - val service: dev.restate.sdk.kotlin.Service = dev.restate.sdk.kotlin.Service.{{#if isObject}}virtualObject{{else}}service{{/if}}(SERVICE_NAME, options) { - {{#handlers}} - {{#if isShared}}sharedHandler{{else if isExclusive}}exclusiveHandler{{else}}handler{{/if}}(dev.restate.sdk.kotlin.Service.HandlerSignature("{{name}}", {{{inputSerdeDecl}}}, {{{outputSerdeDecl}}})) { ctx, req -> - {{#if inputEmpty}}bindableService.{{name}}(ctx){{else}}bindableService.{{name}}(ctx, req){{/if}} - } - {{/handlers}} - } - - override fun options(): dev.restate.sdk.kotlin.Service.Options { - return service.options() - } - - override fun definitions(): List> { - return service.definitions() - } -} \ No newline at end of file diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/BindableServiceFactory.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/BindableServiceFactory.hbs deleted file mode 100644 index c6d64bd9..00000000 --- a/sdk-api-kotlin-gen/src/main/resources/templates/BindableServiceFactory.hbs +++ /dev/null @@ -1,18 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}}{{/if}} - -import dev.restate.sdk.kotlin.Service.Options - -class {{generatedClassSimpleName}}: dev.restate.sdk.common.BindableServiceFactory<{{originalClassFqcn}}, Options> { - - companion object { - const val SERVICE_NAME: String = "{{serviceName}}"; - } - - override fun create(bindableService: {{originalClassFqcn}}): dev.restate.sdk.common.BindableService { - return {{generatedClassSimpleNamePrefix}}BindableService(bindableService); - } - - override fun supports(serviceObject: Any?): Boolean { - return serviceObject is {{originalClassFqcn}}; - } -} \ No newline at end of file diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs deleted file mode 100644 index 21762586..00000000 --- a/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs +++ /dev/null @@ -1,87 +0,0 @@ -{{#if originalClassPkg}}package {{originalClassPkg}};{{/if}} - -import dev.restate.sdk.kotlin.Awaitable -import dev.restate.sdk.kotlin.Context -import dev.restate.sdk.common.StateKey -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.Target -import kotlin.time.Duration -import dev.restate.sdk.kotlin.callSuspend -import dev.restate.sdk.kotlin.sendSuspend - -object {{generatedClassSimpleName}} { - - const val SERVICE_NAME: String = "{{serviceName}}" - - {{#handlers}} - private val {{inputSerdeFieldName}}: Serde<{{{boxedInputFqcn}}}> = {{{inputSerdeDecl}}} - private val {{outputSerdeFieldName}}: Serde<{{{boxedOutputFqcn}}}> = {{{outputSerdeDecl}}} - {{/handlers}} - - fun fromContext(ctx: Context{{#isObject}}, key: String{{/isObject}}): ContextClient { - return ContextClient(ctx{{#isObject}}, key{{/isObject}}) - } - - fun fromIngress(ingressClient: dev.restate.sdk.client.IngressClient{{#isObject}}, key: String{{/isObject}}): IngressClient { - return IngressClient(ingressClient{{#isObject}}, key{{/isObject}}); - } - - fun fromIngress(baseUri: String{{#isObject}}, key: String{{/isObject}}): IngressClient { - return IngressClient(dev.restate.sdk.client.IngressClient.defaultClient(baseUri){{#isObject}}, key{{/isObject}}); - } - - class ContextClient(private val ctx: Context{{#isObject}}, private val key: String{{/isObject}}){ - {{#handlers}} - suspend fun {{methodName}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}): Awaitable<{{{boxedOutputFqcn}}}> { - return this.ctx.callAsync( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, this.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}Unit{{else}}req{{/if}}) - }{{/handlers}} - - fun send(delay: Duration = Duration.ZERO): Send { - return Send(delay) - } - - inner class Send(private val delay: Duration) { - {{#handlers}} - suspend fun {{methodName}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}) { - this@ContextClient.ctx.send( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, this@ContextClient.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}Unit{{else}}req{{/if}}, - delay); - }{{/handlers}} - } - } - - class IngressClient(private val ingressClient: dev.restate.sdk.client.IngressClient{{#isObject}}, private val key: String{{/isObject}}) { - - {{#handlers}} - suspend fun {{methodName}}({{^inputEmpty}}req: {{{inputFqcn}}}, {{/inputEmpty}}requestOptions: dev.restate.sdk.client.RequestOptions = dev.restate.sdk.client.RequestOptions.DEFAULT): {{{boxedOutputFqcn}}} { - return this.ingressClient.callSuspend( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, this.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{outputSerdeFieldName}}, - {{#if inputEmpty}}Unit{{else}}req{{/if}}, - requestOptions); - }{{/handlers}} - - fun send(delay: Duration = Duration.ZERO): Send { - return Send(delay) - } - - inner class Send(private val delay: Duration) { - {{#handlers}} - suspend fun {{methodName}}({{^inputEmpty}}req: {{{inputFqcn}}}, {{/inputEmpty}}requestOptions: dev.restate.sdk.client.RequestOptions = dev.restate.sdk.client.RequestOptions.DEFAULT): String { - return this@IngressClient.ingressClient.sendSuspend( - {{#if isObject}}Target.virtualObject(SERVICE_NAME, this@IngressClient.key, "{{name}}"){{else}}Target.service(SERVICE_NAME, "{{name}}"){{/if}}, - {{inputSerdeFieldName}}, - {{#if inputEmpty}}Unit{{else}}req{{/if}}, - delay, - requestOptions); - }{{/handlers}} - } - } -} \ No newline at end of file diff --git a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt b/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt deleted file mode 100644 index d2c2f5b4..00000000 --- a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.annotation.* -import dev.restate.sdk.annotation.Service -import dev.restate.sdk.common.CoreSerdes -import dev.restate.sdk.common.Target -import dev.restate.sdk.core.ProtoUtils.* -import dev.restate.sdk.core.TestDefinitions -import dev.restate.sdk.core.TestDefinitions.TestDefinition -import dev.restate.sdk.core.TestDefinitions.testInvocation -import java.util.stream.Stream - -class CodegenTest : TestDefinitions.TestSuite { - @Service - class ServiceGreeter { - @Handler - suspend fun greet(context: Context, request: String): String { - return request - } - } - - @VirtualObject - class ObjectGreeter { - @Exclusive - suspend fun greet(context: ObjectContext, request: String): String { - return request - } - - @Handler - @Shared - suspend fun sharedGreet(context: SharedObjectContext, request: String): String { - return request - } - } - - @VirtualObject - interface GreeterInterface { - @Exclusive suspend fun greet(context: ObjectContext, request: String): String - } - - private class ObjectGreeterImplementedFromInterface : GreeterInterface { - override suspend fun greet(context: ObjectContext, request: String): String { - return request - } - } - - @Service(name = "Empty") - class Empty { - @Handler - suspend fun emptyInput(context: Context): String { - val client = EmptyClient.fromContext(context) - return client.emptyInput().await() - } - - @Handler - suspend fun emptyOutput(context: Context, request: String) { - val client = EmptyClient.fromContext(context) - client.emptyOutput(request).await() - } - - @Handler - suspend fun emptyInputOutput(context: Context) { - val client = EmptyClient.fromContext(context) - client.emptyInputOutput().await() - } - } - - @Service(name = "PrimitiveTypes") - class PrimitiveTypes { - @Handler - suspend fun primitiveOutput(context: Context): Int { - val client = PrimitiveTypesClient.fromContext(context) - return client.primitiveOutput().await() - } - - @Handler - suspend fun primitiveInput(context: Context, input: Int) { - val client = PrimitiveTypesClient.fromContext(context) - client.primitiveInput(input).await() - } - } - - @VirtualObject - class CornerCases { - @Exclusive - suspend fun send(context: ObjectContext, request: String): String { - // Just needs to compile - return CodegenTestCornerCasesClient.fromContext(context, request)._send("my_send").await() - } - } - - override fun definitions(): Stream { - return Stream.of( - testInvocation({ ServiceGreeter() }, "greet") - .withInput(startMessage(1), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation({ ObjectGreeter() }, "greet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation({ ObjectGreeter() }, "sharedGreet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation({ ObjectGreeterImplementedFromInterface() }, "greet") - .withInput(startMessage(1, "slinkydeveloper"), inputMessage("Francesco")) - .onlyUnbuffered() - .expectingOutput(outputMessage("Francesco"), END_MESSAGE), - testInvocation({ Empty() }, "emptyInput") - .withInput(startMessage(1), inputMessage(), completionMessage(1, "Till")) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyInput")), - outputMessage("Till"), - END_MESSAGE) - .named("empty output"), - testInvocation({ Empty() }, "emptyOutput") - .withInput( - startMessage(1), - inputMessage("Francesco"), - completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyOutput"), "Francesco"), - outputMessage(), - END_MESSAGE) - .named("empty output"), - testInvocation({ Empty() }, "emptyInputOutput") - .withInput( - startMessage(1), - inputMessage("Francesco"), - completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage(Target.service("Empty", "emptyInputOutput")), - outputMessage(), - END_MESSAGE) - .named("empty input and empty output"), - testInvocation({ PrimitiveTypes() }, "primitiveOutput") - .withInput( - startMessage(1), inputMessage(), completionMessage(1, CoreSerdes.JSON_INT, 10)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage( - Target.service("PrimitiveTypes", "primitiveOutput"), CoreSerdes.VOID, null), - outputMessage(CoreSerdes.JSON_INT, 10), - END_MESSAGE) - .named("primitive output"), - testInvocation({ PrimitiveTypes() }, "primitiveInput") - .withInput( - startMessage(1), inputMessage(10), completionMessage(1).setValue(ByteString.EMPTY)) - .onlyUnbuffered() - .expectingOutput( - invokeMessage( - Target.service("PrimitiveTypes", "primitiveInput"), CoreSerdes.JSON_INT, 10), - outputMessage(), - END_MESSAGE) - .named("primitive input")) - } -} diff --git a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/KtCodegenTests.kt b/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/KtCodegenTests.kt deleted file mode 100644 index f76303ae..00000000 --- a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/KtCodegenTests.kt +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.core.MockMultiThreaded -import dev.restate.sdk.core.MockSingleThread -import dev.restate.sdk.core.TestDefinitions -import dev.restate.sdk.core.TestDefinitions.TestExecutor -import dev.restate.sdk.core.TestRunner -import java.util.stream.Stream - -class KtCodegenTests : TestRunner() { - override fun executors(): Stream { - return Stream.of(MockSingleThread.INSTANCE, MockMultiThreaded.INSTANCE) - } - - public override fun definitions(): Stream { - return Stream.of(CodegenTest()) - } -} diff --git a/sdk-api-kotlin/build.gradle.kts b/sdk-api-kotlin/build.gradle.kts deleted file mode 100644 index ae710d22..00000000 --- a/sdk-api-kotlin/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - java - kotlin("jvm") - kotlin("plugin.serialization") - `library-publishing-conventions` -} - -description = "Restate SDK Kotlin APIs" - -dependencies { - api(project(":sdk-common")) - - implementation(kotlinLibs.kotlinx.coroutines) - implementation(kotlinLibs.kotlinx.serialization.core) - implementation(kotlinLibs.kotlinx.serialization.json) - - implementation(coreLibs.log4j.api) - - testImplementation(project(":sdk-core")) - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - testImplementation(coreLibs.log4j.core) - - testImplementation(project(":sdk-core", "testArchive")) -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Awaitables.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Awaitables.kt deleted file mode 100644 index a174afd4..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Awaitables.kt +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.syscalls.Deferred -import dev.restate.sdk.common.syscalls.Result -import dev.restate.sdk.common.syscalls.Syscalls -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.suspendCancellableCoroutine - -internal abstract class BaseAwaitableImpl -internal constructor(internal val syscalls: Syscalls) : Awaitable { - abstract fun deferred(): Deferred<*> - - abstract suspend fun awaitResult(): Result - - override val onAwait: SelectClause - get() = SelectClauseImpl(this) - - override suspend fun await(): T { - val res = awaitResult() - if (!res.isSuccess) { - throw res.failure!! - } - @Suppress("UNCHECKED_CAST") return res.value as T - } -} - -internal class SingleAwaitableImpl( - syscalls: Syscalls, - private val deferred: Deferred -) : BaseAwaitableImpl(syscalls) { - private var result: Result? = null - - override fun deferred(): Deferred<*> { - return deferred - } - - override suspend fun awaitResult(): Result { - if (!deferred().isCompleted) { - suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveDeferred(deferred(), completingUnitContinuation(cont)) - } - } - if (this.result == null) { - this.result = deferred.toResult() - } - return this.result!! - } -} - -internal abstract class BaseSingleMappedAwaitableImpl( - private val inner: BaseAwaitableImpl -) : BaseAwaitableImpl(inner.syscalls) { - private var mappedResult: Result? = null - - override fun deferred(): Deferred<*> { - return inner.deferred() - } - - abstract suspend fun map(res: Result): Result - - override suspend fun awaitResult(): Result { - if (mappedResult == null) { - this.mappedResult = map(inner.awaitResult()) - } - return mappedResult!! - } -} - -internal open class SingleSerdeAwaitableImpl -internal constructor( - syscalls: Syscalls, - deferred: Deferred, - private val serde: Serde, -) : - BaseSingleMappedAwaitableImpl( - SingleAwaitableImpl(syscalls, deferred), - ) { - @Suppress("UNCHECKED_CAST") - override suspend fun map(res: Result): Result { - return if (res.isSuccess) { - // This propagates exceptions as non-terminal - Result.success(serde.deserializeWrappingException(syscalls, res.value!!)) - } else { - res as Result - } - } -} - -internal class UnitAwakeableImpl(syscalls: Syscalls, deferred: Deferred) : - BaseSingleMappedAwaitableImpl(SingleAwaitableImpl(syscalls, deferred)) { - @Suppress("UNCHECKED_CAST") - override suspend fun map(res: Result): Result { - return if (res.isSuccess) { - Result.success(Unit) - } else { - res as Result - } - } -} - -internal class AnyAwaitableImpl -internal constructor(syscalls: Syscalls, private val awaitables: List>) : - BaseSingleMappedAwaitableImpl( - SingleAwaitableImpl( - syscalls, - syscalls.createAnyDeferred( - awaitables.map { (it as BaseAwaitableImpl<*>).deferred() }))), - AnyAwaitable { - - override suspend fun awaitIndex(): Int { - if (!deferred().isCompleted) { - suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveDeferred(deferred(), completingUnitContinuation(cont)) - } - } - - return deferred().toResult()!!.value as Int - } - - @Suppress("UNCHECKED_CAST") - override suspend fun map(res: Result): Result { - return if (res.isSuccess) - ((awaitables[res.value!!] as BaseAwaitableImpl<*>).awaitResult() as Result) - else (res as Result) - } -} - -internal fun wrapAllAwaitable(awaitables: List>): Awaitable { - val syscalls = (awaitables.get(0) as BaseAwaitableImpl<*>).syscalls - return UnitAwakeableImpl( - syscalls, - syscalls.createAllDeferred(awaitables.map { (it as BaseAwaitableImpl<*>).deferred() }), - ) -} - -internal fun wrapAnyAwaitable(awaitables: List>): AnyAwaitable { - val syscalls = (awaitables.get(0) as BaseAwaitableImpl<*>).syscalls - return AnyAwaitableImpl(syscalls, awaitables) -} - -internal class AwakeableImpl -internal constructor( - syscalls: Syscalls, - deferred: Deferred, - serde: Serde, - override val id: String -) : SingleSerdeAwaitableImpl(syscalls, deferred, serde), Awakeable {} - -internal class AwakeableHandleImpl(val syscalls: Syscalls, val id: String) : AwakeableHandle { - override suspend fun resolve(serde: Serde, payload: T) { - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveAwakeable( - id, serde.serializeWrappingException(syscalls, payload), completingUnitContinuation(cont)) - } - } - - override suspend fun reject(reason: String) { - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.rejectAwakeable(id, reason, completingUnitContinuation(cont)) - } - } -} - -internal class SelectClauseImpl(override val awaitable: Awaitable) : SelectClause - -@PublishedApi -internal class SelectImplementation : SelectBuilder { - - private val clauses: MutableList, suspend (Any?) -> R>> = mutableListOf() - - @Suppress("UNCHECKED_CAST") - override fun SelectClause.invoke(block: suspend (T) -> R) { - clauses.add(this.awaitable as Awaitable<*> to block as suspend (Any?) -> R) - } - - suspend fun doSelect(): R { - val index = wrapAnyAwaitable(clauses.map { it.first }).awaitIndex() - val resolved = clauses[index] - return resolved.first.await().let { resolved.second(it) } - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt deleted file mode 100644 index 9c98bcaa..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/ContextImpl.kt +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.* -import dev.restate.sdk.common.Target -import dev.restate.sdk.common.syscalls.Deferred -import dev.restate.sdk.common.syscalls.EnterSideEffectSyscallCallback -import dev.restate.sdk.common.syscalls.ExitSideEffectSyscallCallback -import dev.restate.sdk.common.syscalls.Syscalls -import kotlin.coroutines.resume -import kotlin.time.Duration -import kotlin.time.toJavaDuration -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.suspendCancellableCoroutine - -internal class ContextImpl internal constructor(private val syscalls: Syscalls) : ObjectContext { - override fun key(): String { - return this.syscalls.objectKey() - } - - override fun request(): Request { - return this.syscalls.request() - } - - override suspend fun get(key: StateKey): T? { - val deferred: Deferred = - suspendCancellableCoroutine { cont: CancellableContinuation> -> - syscalls.get(key.name(), completingContinuation(cont)) - } - - if (!deferred.isCompleted) { - suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveDeferred(deferred, completingUnitContinuation(cont)) - } - } - - val readyResult = deferred.toResult()!! - if (!readyResult.isSuccess) { - throw readyResult.failure!! - } - if (readyResult.isEmpty) { - return null - } - return key.serde().deserializeWrappingException(syscalls, readyResult.value!!)!! - } - - override suspend fun stateKeys(): Collection { - val deferred: Deferred> = - suspendCancellableCoroutine { cont: CancellableContinuation>> -> - syscalls.getKeys(completingContinuation(cont)) - } - - if (!deferred.isCompleted) { - suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.resolveDeferred(deferred, completingUnitContinuation(cont)) - } - } - - val readyResult = deferred.toResult()!! - if (!readyResult.isSuccess) { - throw readyResult.failure!! - } - return readyResult.value!! - } - - override suspend fun set(key: StateKey, value: T) { - val serializedValue = key.serde().serializeWrappingException(syscalls, value)!! - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.set(key.name(), serializedValue, completingUnitContinuation(cont)) - } - } - - override suspend fun clear(key: StateKey<*>) { - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.clear(key.name(), completingUnitContinuation(cont)) - } - } - - override suspend fun clearAll() { - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.clearAll(completingUnitContinuation(cont)) - } - } - - override suspend fun timer(duration: Duration): Awaitable { - val deferred: Deferred = - suspendCancellableCoroutine { cont: CancellableContinuation> -> - syscalls.sleep(duration.toJavaDuration(), completingContinuation(cont)) - } - - return UnitAwakeableImpl(syscalls, deferred) - } - - override suspend fun callAsync( - target: Target, - inputSerde: Serde, - outputSerde: Serde, - parameter: T - ): Awaitable { - val input = inputSerde.serializeWrappingException(syscalls, parameter) - - val deferred: Deferred = - suspendCancellableCoroutine { cont: CancellableContinuation> -> - syscalls.call(target, input, completingContinuation(cont)) - } - - return SingleSerdeAwaitableImpl(syscalls, deferred, outputSerde) - } - - override suspend fun send( - target: Target, - inputSerde: Serde, - parameter: T, - delay: Duration - ) { - val input = inputSerde.serializeWrappingException(syscalls, parameter) - - return suspendCancellableCoroutine { cont: CancellableContinuation -> - syscalls.send(target, input, delay.toJavaDuration(), completingUnitContinuation(cont)) - } - } - - override suspend fun runBlock( - serde: Serde, - name: String, - block: suspend () -> T - ): T { - val exitResult = - suspendCancellableCoroutine { cont: CancellableContinuation> - -> - syscalls.enterSideEffectBlock( - name, - object : EnterSideEffectSyscallCallback { - override fun onSuccess(t: ByteString?) { - val deferred: CompletableDeferred = CompletableDeferred() - deferred.complete(t!!) - cont.resume(deferred) - } - - override fun onFailure(t: TerminalException) { - val deferred: CompletableDeferred = CompletableDeferred() - deferred.completeExceptionally(t) - cont.resume(deferred) - } - - override fun onCancel(t: Throwable?) { - cont.cancel(t) - } - - override fun onNotExecuted() { - cont.resume(CompletableDeferred()) - } - }) - } - - if (exitResult.isCompleted) { - return serde.deserializeWrappingException(syscalls, exitResult.await())!! - } - - var actionReturnValue: T? = null - var actionFailure: TerminalException? = null - try { - actionReturnValue = block() - } catch (e: TerminalException) { - actionFailure = e - } catch (e: Error) { - throw e - } catch (t: Throwable) { - syscalls.fail(t) - throw CancellationException("Side effect failure", t) - } - - val exitCallback = - object : ExitSideEffectSyscallCallback { - override fun onSuccess(t: ByteString?) { - exitResult.complete(t!!) - } - - override fun onFailure(t: TerminalException) { - exitResult.completeExceptionally(t) - } - - override fun onCancel(t: Throwable?) { - exitResult.cancel(CancellationException("Suspended", t)) - } - } - - if (actionFailure != null) { - syscalls.exitSideEffectBlockWithTerminalException(actionFailure, exitCallback) - } else { - syscalls.exitSideEffectBlock( - serde.serializeWrappingException(syscalls, actionReturnValue), exitCallback) - } - - return serde.deserializeWrappingException(syscalls, exitResult.await()) - } - - override suspend fun awakeable(serde: Serde): Awakeable { - val (aid, deferredResult) = - suspendCancellableCoroutine { - cont: CancellableContinuation>> -> - syscalls.awakeable(completingContinuation(cont)) - } - - return AwakeableImpl(syscalls, deferredResult, serde, aid) - } - - override fun awakeableHandle(id: String): AwakeableHandle { - return AwakeableHandleImpl(syscalls, id) - } - - override fun random(): RestateRandom { - return RestateRandom(syscalls.request().invocationId().toRandomSeed(), syscalls) - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/KtSerdes.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/KtSerdes.kt deleted file mode 100644 index 321da9e8..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/KtSerdes.kt +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.StateKey -import java.nio.charset.StandardCharsets -import kotlin.reflect.typeOf -import kotlinx.serialization.KSerializer -import kotlinx.serialization.json.Json -import kotlinx.serialization.serializer - -object KtStateKey { - - /** Creates a json [StateKey]. */ - inline fun json(name: String): StateKey { - return StateKey.of(name, KtSerdes.json()) - } -} - -object KtSerdes { - - /** Creates a [Serde] implementation using the `kotlinx.serialization` json module. */ - inline fun json(): Serde { - @Suppress("UNCHECKED_CAST") - return when (typeOf()) { - typeOf() -> UNIT as Serde - else -> json(serializer()) - } - } - - val UNIT: Serde = - object : Serde { - override fun serialize(value: Unit?): ByteArray { - return ByteArray(0) - } - - override fun serializeToByteString(value: Unit?): ByteString { - return ByteString.EMPTY - } - - override fun deserialize(value: ByteArray) { - return - } - - override fun deserialize(byteString: ByteString) { - return - } - } - - /** Creates a [Serde] implementation using the `kotlinx.serialization` json module. */ - fun json(serializer: KSerializer): Serde { - return object : Serde { - override fun serialize(value: T?): ByteArray { - return Json.encodeToString(serializer, value!!).encodeToByteArray() - } - - override fun deserialize(value: ByteArray?): T { - return Json.decodeFromString(serializer, String(value!!, StandardCharsets.UTF_8)) - } - } - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Service.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Service.kt deleted file mode 100644 index da5cddfe..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Service.kt +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.* -import dev.restate.sdk.common.syscalls.* -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.asContextElement -import kotlinx.coroutines.launch -import org.apache.logging.log4j.LogManager - -class Service -private constructor( - fqsn: String, - isKeyed: Boolean, - handlers: Map>, - private val options: Options -) : BindableService { - private val serviceDefinition = - ServiceDefinition( - fqsn, - if (isKeyed) ServiceType.VIRTUAL_OBJECT else ServiceType.SERVICE, - handlers.values.map { obj: Handler<*, *, *> -> obj.toHandlerDefinition() }) - - override fun options(): Options { - return this.options - } - - override fun definitions() = listOf(this.serviceDefinition) - - companion object { - fun service( - name: String, - options: Options = Options.DEFAULT, - init: ServiceBuilder.() -> Unit - ): Service { - val builder = ServiceBuilder(name) - builder.init() - return builder.build(options) - } - - fun virtualObject( - name: String, - options: Options = Options.DEFAULT, - init: VirtualObjectBuilder.() -> Unit - ): Service { - val builder = VirtualObjectBuilder(name) - builder.init() - return builder.build(options) - } - } - - class VirtualObjectBuilder internal constructor(private val name: String) { - private val handlers: MutableMap> = mutableMapOf() - - fun sharedHandler( - sig: HandlerSignature, - runner: suspend (ObjectContext, REQ) -> RES - ): VirtualObjectBuilder { - handlers[sig.name] = Handler(sig, HandlerType.SHARED, runner) - return this - } - - inline fun sharedHandler( - name: String, - noinline runner: suspend (ObjectContext, REQ) -> RES - ) = this.sharedHandler(HandlerSignature(name, KtSerdes.json(), KtSerdes.json()), runner) - - fun exclusiveHandler( - sig: HandlerSignature, - runner: suspend (ObjectContext, REQ) -> RES - ): VirtualObjectBuilder { - handlers[sig.name] = Handler(sig, HandlerType.EXCLUSIVE, runner) - return this - } - - inline fun exclusiveHandler( - name: String, - noinline runner: suspend (ObjectContext, REQ) -> RES - ) = this.exclusiveHandler(HandlerSignature(name, KtSerdes.json(), KtSerdes.json()), runner) - - fun build(options: Options) = Service(this.name, true, this.handlers, options) - } - - class ServiceBuilder internal constructor(private val name: String) { - private val handlers: MutableMap> = mutableMapOf() - - fun handler( - sig: HandlerSignature, - runner: suspend (Context, REQ) -> RES - ): ServiceBuilder { - handlers[sig.name] = Handler(sig, HandlerType.SHARED, runner) - return this - } - - inline fun handler( - name: String, - noinline runner: suspend (Context, REQ) -> RES - ) = this.handler(HandlerSignature(name, KtSerdes.json(), KtSerdes.json()), runner) - - fun build(options: Options) = Service(this.name, false, this.handlers, options) - } - - class Handler( - private val handlerSignature: HandlerSignature, - private val handlerType: HandlerType, - private val runner: suspend (CTX, REQ) -> RES, - ) : InvocationHandler { - - companion object { - private val LOG = LogManager.getLogger() - } - - fun toHandlerDefinition() = - HandlerDefinition( - handlerSignature.name, - handlerType, - handlerSignature.requestSerde.schema(), - handlerSignature.responseSerde.schema(), - this) - - override fun handle( - syscalls: Syscalls, - options: Options, - callback: SyscallCallback - ) { - val ctx: Context = ContextImpl(syscalls) - - val scope = - CoroutineScope( - options.coroutineContext + - InvocationHandler.SYSCALLS_THREAD_LOCAL.asContextElement(syscalls)) - scope.launch { - val serializedResult: ByteString - - try { - // Parse input - val req: REQ - try { - req = handlerSignature.requestSerde.deserialize(syscalls.request().bodyBuffer()) - } catch (e: Error) { - throw e - } catch (e: Throwable) { - LOG.warn("Error when deserializing input", e) - throw TerminalException( - TerminalException.BAD_REQUEST_CODE, "Cannot deserialize input: " + e.message) - } - - // Execute user code - @Suppress("UNCHECKED_CAST") val res: RES = runner(ctx as CTX, req) - - // Serialize output - try { - serializedResult = handlerSignature.responseSerde.serializeToByteString(res) - } catch (e: Error) { - throw e - } catch (e: Throwable) { - LOG.warn("Error when serializing input", e) - throw TerminalException( - TerminalException.INTERNAL_SERVER_ERROR_CODE, "Cannot serialize output: $e") - } - } catch (e: Throwable) { - callback.onCancel(e) - return@launch - } - - // Complete callback - callback.onSuccess(serializedResult) - } - } - } - - class HandlerSignature( - val name: String, - val requestSerde: Serde, - val responseSerde: Serde - ) - - class Options(val coroutineContext: CoroutineContext) { - companion object { - val DEFAULT: Options = Options(Dispatchers.Default) - } - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Util.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Util.kt deleted file mode 100644 index 148e32f8..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/Util.kt +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import com.google.protobuf.ByteString -import dev.restate.sdk.common.Serde -import dev.restate.sdk.common.syscalls.SyscallCallback -import dev.restate.sdk.common.syscalls.Syscalls -import kotlin.coroutines.resume -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CancellationException - -internal fun completingContinuation(cont: CancellableContinuation): SyscallCallback { - return SyscallCallback.of(cont::resume) { - cont.cancel(CancellationException("Restate internal error", it)) - } -} - -internal fun completingUnitContinuation( - cont: CancellableContinuation -): SyscallCallback { - return SyscallCallback.of( - { cont.resume(Unit) }, { cont.cancel(CancellationException("Restate internal error", it)) }) -} - -internal fun Serde.serializeWrappingException( - syscalls: Syscalls, - value: T? -): ByteString? { - return try { - this.serializeToByteString(value) - } catch (e: Exception) { - syscalls.fail(e) - throw CancellationException("Failed serialization", e) - } -} - -internal fun Serde.deserializeWrappingException( - syscalls: Syscalls, - byteString: ByteString -): T { - return try { - this.deserialize(byteString) - } catch (e: Exception) { - syscalls.fail(e) - throw CancellationException("Failed deserialization", e) - } -} diff --git a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt b/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt deleted file mode 100644 index 12084ce8..00000000 --- a/sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/api.kt +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.kotlin - -import dev.restate.sdk.common.* -import dev.restate.sdk.common.Target -import dev.restate.sdk.common.syscalls.Syscalls -import java.util.* -import kotlin.random.Random -import kotlin.time.Duration - -/** - * This interface exposes the Restate functionalities to Restate services. It can be used to - * interact with other Restate services, record non-deterministic closures, execute timers and - * synchronize with external systems. - * - * All methods of this interface, and related interfaces, throws either [TerminalException] or - * cancels the coroutine. [TerminalException] can be caught and acted upon. - * - * NOTE: This interface MUST NOT be accessed concurrently since it can lead to different orderings - * of user actions, corrupting the execution of the invocation. - */ -sealed interface Context { - - fun request(): Request - - /** - * Causes the current execution of the function invocation to sleep for the given duration. - * - * @param duration for which to sleep. - */ - suspend fun sleep(duration: Duration) { - timer(duration).await() - } - - /** - * Causes the start of a timer for the given duration. You can await on the timer end by invoking - * [Awaitable.await]. - * - * @param duration for which to sleep. - */ - suspend fun timer(duration: Duration): Awaitable - - /** - * Invoke another Restate service method and wait for the response. Same as - * `call(methodDescriptor, parameter).await()`. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param outputSerde Output serde - * @param parameter the invocation request parameter. - * @return the invocation response. - */ - suspend fun call( - target: Target, - inputSerde: Serde, - outputSerde: Serde, - parameter: T - ): R { - return callAsync(target, inputSerde, outputSerde, parameter).await() - } - - /** - * Invoke another Restate service method. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param outputSerde Output serde - * @param parameter the invocation request parameter. - * @return an [Awaitable] that wraps the Restate service method result. - */ - suspend fun callAsync( - target: Target, - inputSerde: Serde, - outputSerde: Serde, - parameter: T - ): Awaitable - - /** - * Invoke another Restate service without waiting for the response. - * - * @param target the address of the callee - * @param inputSerde Input serde - * @param parameter the invocation request parameter. - * @param delay time to wait before executing the call - */ - suspend fun send( - target: Target, - inputSerde: Serde, - parameter: T, - delay: Duration = Duration.ZERO - ) - - /** - * Execute a non-deterministic closure, recording the result value in the journal. The result - * value will be re-played in case of re-invocation (e.g. because of failure recovery or - * suspension point) without re-executing the closure. Use this feature if you want to perform - * non-deterministic operations. - * - * You can name this closure using the `name` parameter. This name will be available in the - * observability tools. - * - *

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

Error handling

- * - * Errors occurring within this closure won't be propagated to the caller, unless they are - * [TerminalException]. Consider the following code: - * ``` - * // Bad usage of try-catch outside the runBlock - * try { - * ctx.runBlock { - * throw IllegalStateException(); - * }; - * } catch (e: IllegalStateException) { - * // This will never be executed, - * // but the error will be retried by Restate, - * // following the invocation retry policy. - * } - * - * // Good usage of try-catch outside the runBlock - * try { - * ctx.runBlock { - * throw TerminalException("my error"); - * }; - * } catch (e: TerminalException) { - * // This is invoked - * } - * ``` - * - * To propagate failures to the run call-site, make sure to wrap them in [TerminalException]. - * - * @param serde the type tag of the return value, used to serialize/deserialize it. - * @param name the name of the side effect. - * @param block closure to execute. - * @param T type of the return value. - * @return value of the runBlock operation. - */ - suspend fun runBlock(serde: Serde, name: String = "", block: suspend () -> T): T - - /** - * Create an [Awakeable], addressable through [Awakeable.id]. - * - * You can use this feature to implement external asynchronous systems interactions, for example - * you can send a Kafka record including the [Awakeable.id], and then let another service consume - * from Kafka the responses of given external system interaction by using [awakeableHandle]. - * - * @param serde the response type tag to use for deserializing the [Awakeable] result. - * @return the [Awakeable] to await on. - * @see Awakeable - */ - suspend fun awakeable(serde: Serde): Awakeable - - /** - * Create a new [AwakeableHandle] for the provided identifier. You can use it to - * [AwakeableHandle.resolve] or [AwakeableHandle.reject] the linked [Awakeable]. - * - * @see Awakeable - */ - fun awakeableHandle(id: String): AwakeableHandle - - /** - * Create a [RestateRandom] instance inherently predictable, seeded on the - * [dev.restate.sdk.common.InvocationId], which is not secret. - * - * This instance is useful to generate identifiers, idempotency keys, and for uniform sampling - * from a set of options. If a cryptographically secure value is needed, please generate that - * externally using [runBlock]. - * - * You MUST NOT use this [Random] instance inside a [runBlock]. - * - * @return the [Random] instance. - */ - fun random(): RestateRandom -} - -/** - * Execute a non-deterministic closure, recording the result value in the journal using - * [KtSerdes.json]. The result value will be re-played in case of re-invocation (e.g. because of - * failure recovery or suspension point) without re-executing the closure. Use this feature if you - * want to perform non-deterministic operations. - * - *

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

Error handling

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

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

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

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

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

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

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

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

NOTE: This interface MUST NOT be accessed concurrently since it can lead to different - * orderings of user actions, corrupting the execution of the invocation. - */ -public final class Awakeable extends Awaitable.MappedAwaitable { - - private final String identifier; - - Awakeable(Syscalls syscalls, Deferred deferred, Serde serde, String identifier) { - super( - Awaitable.single(syscalls, deferred), - res -> { - if (res.isSuccess()) { - return Result.success( - Util.deserializeWrappingException(syscalls, serde, res.getValue())); - } - //noinspection unchecked - return (Result) res; - }); - this.identifier = identifier; - } - - /** - * @return the unique identifier of this {@link Awakeable} instance. - */ - public String id() { - return identifier; - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/AwakeableHandle.java b/sdk-api/src/main/java/dev/restate/sdk/AwakeableHandle.java deleted file mode 100644 index d335564b..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/AwakeableHandle.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.Serde; -import org.jspecify.annotations.NonNull; - -/** This class represents a handle to an {@link Awakeable} created in another service. */ -public interface AwakeableHandle { - - /** - * Complete with success the {@link Awakeable}. - * - * @param serde used to serialize the {@link Awakeable} result payload. - * @param payload the result payload. MUST NOT be null. - * @see Awakeable - */ - void resolve(Serde serde, @NonNull T payload); - - /** - * Complete with failure the {@link Awakeable}. - * - * @param reason the rejection reason. MUST NOT be null. - * @see Awakeable - */ - void reject(String reason); -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/Context.java b/sdk-api/src/main/java/dev/restate/sdk/Context.java deleted file mode 100644 index dd01b59b..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/Context.java +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.function.ThrowingRunnable; -import dev.restate.sdk.common.function.ThrowingSupplier; -import java.time.Duration; - -/** - * This interface exposes the Restate functionalities to Restate services. It can be used to - * interact with other Restate services, record non-deterministic closures, execute timers and - * synchronize with external systems. - * - *

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

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

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

You can name this closure using the {@code name} parameter. This name will be available in - * the observability tools. - * - *

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

Error handling

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

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

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

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

You MUST NOT use this object inside a {@link ObjectContext#run(Serde, ThrowingSupplier)}. - */ -public class RestateRandom extends Random { - - private final Syscalls syscalls; - private boolean seedInitialized = false; - - RestateRandom(long randomSeed, Syscalls syscalls) { - super(randomSeed); - this.syscalls = syscalls; - } - - /** - * @throws UnsupportedOperationException You cannot set the seed on RestateRandom - */ - @Override - public synchronized void setSeed(long seed) { - if (seedInitialized) { - throw new UnsupportedOperationException("You cannot set the seed on RestateRandom"); - } - super.setSeed(seed); - this.seedInitialized = true; - } - - /** - * @return a UUID generated using this RNG. - */ - public UUID nextUUID() { - return new UUID(this.nextLong(), this.nextLong()); - } - - @Override - protected int next(int bits) { - if (this.syscalls.isInsideSideEffect()) { - throw new IllegalStateException("You can't use RestateRandom inside ctx.run!"); - } - - return super.next(bits); - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/Service.java b/sdk-api/src/main/java/dev/restate/sdk/Service.java deleted file mode 100644 index 3f0ccc14..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/Service.java +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.ServiceType; -import dev.restate.sdk.common.syscalls.*; -import java.util.*; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.function.BiFunction; -import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public final class Service implements BindableService { - private final ServiceDefinition serviceDefinition; - private final Service.Options options; - - private Service( - String fqsn, boolean isKeyed, HashMap> handlers, Options options) { - this.serviceDefinition = - new ServiceDefinition<>( - fqsn, - isKeyed ? ServiceType.VIRTUAL_OBJECT : ServiceType.SERVICE, - handlers.values().stream() - .map(Handler::toHandlerDefinition) - .collect(Collectors.toList())); - this.options = options; - } - - @Override - public Options options() { - return this.options; - } - - @Override - public List> definitions() { - return List.of(this.serviceDefinition); - } - - public static ServiceBuilder service(String name) { - return new ServiceBuilder(name); - } - - public static VirtualObjectBuilder virtualObject(String name) { - return new VirtualObjectBuilder(name); - } - - public static class AbstractServiceBuilder { - - protected final String name; - protected final HashMap> handlers; - - public AbstractServiceBuilder(String name) { - this.name = name; - this.handlers = new HashMap<>(); - } - } - - public static class VirtualObjectBuilder extends AbstractServiceBuilder { - - VirtualObjectBuilder(String name) { - super(name); - } - - public VirtualObjectBuilder withShared( - HandlerSignature sig, BiFunction runner) { - this.handlers.put(sig.getName(), new Handler<>(sig, HandlerType.SHARED, runner)); - return this; - } - - public VirtualObjectBuilder withExclusive( - HandlerSignature sig, BiFunction runner) { - this.handlers.put(sig.getName(), new Handler<>(sig, HandlerType.EXCLUSIVE, runner)); - return this; - } - - public Service build(Service.Options options) { - return new Service(this.name, true, this.handlers, options); - } - } - - public static class ServiceBuilder extends AbstractServiceBuilder { - - ServiceBuilder(String name) { - super(name); - } - - public ServiceBuilder with( - HandlerSignature sig, BiFunction runner) { - this.handlers.put(sig.getName(), new Handler<>(sig, HandlerType.SHARED, runner)); - return this; - } - - public Service build(Service.Options options) { - return new Service(this.name, false, this.handlers, options); - } - } - - @SuppressWarnings("unchecked") - public static class Handler implements InvocationHandler { - private final HandlerSignature handlerSignature; - private final HandlerType handlerType; - private final BiFunction runner; - - private static final Logger LOG = LogManager.getLogger(Handler.class); - - public Handler( - HandlerSignature handlerSignature, - HandlerType handlerType, - BiFunction runner) { - this.handlerSignature = handlerSignature; - this.handlerType = handlerType; - this.runner = (BiFunction) runner; - } - - public HandlerSignature getHandlerSignature() { - return handlerSignature; - } - - public BiFunction getRunner() { - return runner; - } - - public HandlerDefinition toHandlerDefinition() { - return new HandlerDefinition<>( - this.handlerSignature.name, - this.handlerType, - this.handlerSignature.requestSerde.schema(), - this.handlerSignature.responseSerde.schema(), - this); - } - - @Override - public void handle( - Syscalls syscalls, Service.Options options, SyscallCallback callback) { - // Wrap the executor for setting/unsetting the thread local - Executor wrapped = - runnable -> - options.executor.execute( - () -> { - SYSCALLS_THREAD_LOCAL.set(syscalls); - try { - runnable.run(); - } finally { - SYSCALLS_THREAD_LOCAL.remove(); - } - }); - wrapped.execute( - () -> { - // Any context switching, if necessary, will be done by ResolvedEndpointHandler - Context ctx = new ContextImpl(syscalls); - - // Parse input - REQ req; - try { - req = this.handlerSignature.requestSerde.deserialize(syscalls.request().bodyBuffer()); - } catch (Error e) { - throw e; - } catch (Throwable e) { - LOG.warn("Cannot deserialize input", e); - callback.onCancel( - new TerminalException( - TerminalException.BAD_REQUEST_CODE, - "Cannot deserialize input: " + e.getMessage())); - return; - } - - // Execute user code - RES res; - try { - res = this.runner.apply(ctx, req); - } catch (Error e) { - throw e; - } catch (Throwable e) { - callback.onCancel(e); - return; - } - - // Serialize output - ByteString serializedResult; - try { - serializedResult = this.handlerSignature.responseSerde.serializeToByteString(res); - } catch (Error e) { - throw e; - } catch (Throwable e) { - LOG.warn("Cannot serialize output", e); - callback.onCancel( - new TerminalException( - TerminalException.INTERNAL_SERVER_ERROR_CODE, - "Cannot serialize output: " + e.getMessage())); - return; - } - - // Complete callback - callback.onSuccess(serializedResult); - }); - } - } - - public static class HandlerSignature { - - private final String name; - private final Serde requestSerde; - private final Serde responseSerde; - - HandlerSignature(String name, Serde requestSerde, Serde responseSerde) { - this.name = name; - this.requestSerde = requestSerde; - this.responseSerde = responseSerde; - } - - public static HandlerSignature of( - String method, Serde requestSerde, Serde responseSerde) { - return new HandlerSignature<>(method, requestSerde, responseSerde); - } - - public String getName() { - return name; - } - - public Serde getRequestSerde() { - return requestSerde; - } - - public Serde getResponseSerde() { - return responseSerde; - } - } - - public static class Options { - public static final Options DEFAULT = new Options(Executors.newCachedThreadPool()); - - private final Executor executor; - - /** - * You can run on virtual threads by using the executor {@code - * Executors.newVirtualThreadPerTaskExecutor()}. - */ - public Options(Executor executor) { - this.executor = executor; - } - } -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/SharedObjectContext.java b/sdk-api/src/main/java/dev/restate/sdk/SharedObjectContext.java deleted file mode 100644 index 2f095c09..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/SharedObjectContext.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.StateKey; -import java.util.Collection; -import java.util.Optional; - -/** - * This interface extends {@link Context} adding access to the virtual object instance key-value - * state storage - * - *

NOTE: This interface MUST NOT be accessed concurrently since it can lead to different - * orderings of user actions, corrupting the execution of the invocation. - * - * @see Context - */ -public interface SharedObjectContext extends Context { - /** - * @return the key of this object - */ - String key(); - - /** - * Gets the state stored under key, deserializing the raw value using the {@link Serde} in the - * {@link StateKey}. - * - * @param key identifying the state to get and its type. - * @return an {@link Optional} containing the stored state deserialized or an empty {@link - * Optional} if not set yet. - * @throws RuntimeException when the state cannot be deserialized. - */ - Optional get(StateKey key); - - /** - * Gets all the known state keys for this virtual object instance. - * - * @return the immutable collection of known state keys. - */ - Collection stateKeys(); -} diff --git a/sdk-api/src/main/java/dev/restate/sdk/Util.java b/sdk-api/src/main/java/dev/restate/sdk/Util.java deleted file mode 100644 index fe055895..00000000 --- a/sdk-api/src/main/java/dev/restate/sdk/Util.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.AbortedExecutionException; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.function.ThrowingFunction; -import dev.restate.sdk.common.syscalls.Deferred; -import dev.restate.sdk.common.syscalls.Result; -import dev.restate.sdk.common.syscalls.SyscallCallback; -import dev.restate.sdk.common.syscalls.Syscalls; -import java.util.Optional; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; - -class Util { - - private Util() {} - - static T blockOnResolve(Syscalls syscalls, Deferred deferred) { - if (!deferred.isCompleted()) { - Util.blockOnSyscall(cb -> syscalls.resolveDeferred(deferred, cb)); - } - - return Util.unwrapResult(deferred.toResult()); - } - - static T awaitCompletableFuture(CompletableFuture future) { - try { - return future.get(); - } catch (InterruptedException | CancellationException e) { - AbortedExecutionException.sneakyThrow(); - return null; // Previous statement throws an exception - } catch (ExecutionException e) { - throw (RuntimeException) e.getCause(); - } - } - - static T blockOnSyscall(Consumer> syscallExecutor) { - CompletableFuture fut = new CompletableFuture<>(); - syscallExecutor.accept(SyscallCallback.completingFuture(fut)); - return Util.awaitCompletableFuture(fut); - } - - static T unwrapResult(Result res) { - if (res.isSuccess()) { - return res.getValue(); - } - throw res.getFailure(); - } - - static Optional unwrapOptionalReadyResult(Result res) { - if (!res.isSuccess()) { - throw res.getFailure(); - } - if (res.isEmpty()) { - return Optional.empty(); - } - return Optional.of(res.getValue()); - } - - static R executeMappingException(Syscalls syscalls, ThrowingFunction fn, T t) { - try { - return fn.apply(t); - } catch (Throwable e) { - syscalls.fail(e); - AbortedExecutionException.sneakyThrow(); - return null; - } - } - - static ByteString serializeWrappingException(Syscalls syscalls, Serde serde, T value) { - return executeMappingException(syscalls, serde::serializeToByteString, value); - } - - static T deserializeWrappingException( - Syscalls syscalls, Serde serde, ByteString byteString) { - return executeMappingException(syscalls, serde::deserialize, byteString); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java b/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java deleted file mode 100644 index 4d9d78c8..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/AwakeableIdTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.AwakeableIdTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; - -public class AwakeableIdTest extends AwakeableIdTestSuite { - - protected TestInvocationBuilder returnAwakeableId() { - return testDefinitionForService( - "ReturnAwakeableId", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> context.awakeable(CoreSerdes.JSON_STRING).id()); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/DeferredTest.java b/sdk-api/src/test/java/dev/restate/sdk/DeferredTest.java deleted file mode 100644 index 4e73bb1a..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/DeferredTest.java +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.*; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.core.DeferredTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.time.Duration; -import java.util.concurrent.TimeoutException; - -public class DeferredTest extends DeferredTestSuite { - - protected TestInvocationBuilder reverseAwaitOrder() { - return testDefinitionForVirtualObject( - "ReverseAwaitOrder", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> { - Awaitable a1 = callGreeterGreetService(context, "Francesco"); - Awaitable a2 = callGreeterGreetService(context, "Till"); - - String a2Res = a2.await(); - context.set(StateKey.of("A2", CoreSerdes.JSON_STRING), a2Res); - - String a1Res = a1.await(); - - return a1Res + "-" + a2Res; - }); - } - - protected TestInvocationBuilder awaitTwiceTheSameAwaitable() { - return testDefinitionForService( - "AwaitTwiceTheSameAwaitable", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> { - Awaitable a = callGreeterGreetService(context, "Francesco"); - - return a.await() + "-" + a.await(); - }); - } - - protected TestInvocationBuilder awaitAll() { - return testDefinitionForService( - "AwaitAll", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> { - Awaitable a1 = callGreeterGreetService(context, "Francesco"); - Awaitable a2 = callGreeterGreetService(context, "Till"); - - Awaitable.all(a1, a2).await(); - - return a1.await() + "-" + a2.await(); - }); - } - - protected TestInvocationBuilder awaitAny() { - return testDefinitionForService( - "AwaitAny", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (context, unused) -> { - Awaitable a1 = callGreeterGreetService(context, "Francesco"); - Awaitable a2 = callGreeterGreetService(context, "Till"); - - return (String) Awaitable.any(a1, a2).await(); - }); - } - - protected TestInvocationBuilder combineAnyWithAll() { - return testDefinitionForService( - "CombineAnyWithAll", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a3 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a4 = ctx.awakeable(CoreSerdes.JSON_STRING); - - Awaitable a12 = Awaitable.any(a1, a2); - Awaitable a23 = Awaitable.any(a2, a3); - Awaitable a34 = Awaitable.any(a3, a4); - Awaitable.all(a12, a23, a34).await(); - - return a12.await() + (String) a23.await() + a34.await(); - }); - } - - protected TestInvocationBuilder awaitAnyIndex() { - return testDefinitionForService( - "AwaitAnyIndex", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a3 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a4 = ctx.awakeable(CoreSerdes.JSON_STRING); - - return String.valueOf(Awaitable.any(a1, Awaitable.all(a2, a3), a4).awaitIndex()); - }); - } - - protected TestInvocationBuilder awaitOnAlreadyResolvedAwaitables() { - return testDefinitionForService( - "AwaitOnAlreadyResolvedAwaitables", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - Awaitable a1 = ctx.awakeable(CoreSerdes.JSON_STRING); - Awaitable a2 = ctx.awakeable(CoreSerdes.JSON_STRING); - - Awaitable a12 = Awaitable.all(a1, a2); - Awaitable a12and1 = Awaitable.all(a12, a1); - Awaitable a121and12 = Awaitable.all(a12and1, a12); - - a12and1.await(); - a121and12.await(); - - return a1.await() + a2.await(); - }); - } - - protected TestInvocationBuilder awaitWithTimeout() { - return testDefinitionForService( - "AwaitOnAlreadyResolvedAwaitables", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - Awaitable call = callGreeterGreetService(ctx, "Francesco"); - - String result; - try { - result = call.await(Duration.ofDays(1)); - } catch (TimeoutException e) { - result = "timeout"; - } - - return result; - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java b/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java deleted file mode 100644 index 19aba56c..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/EagerStateTest.java +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForVirtualObject; -import static org.assertj.core.api.Assertions.assertThat; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.core.EagerStateTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; - -public class EagerStateTest extends EagerStateTestSuite { - - protected TestInvocationBuilder getEmpty() { - return testDefinitionForVirtualObject( - "GetEmpty", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> - String.valueOf(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).isEmpty())); - } - - protected TestInvocationBuilder get() { - return testDefinitionForVirtualObject( - "GetEmpty", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get()); - } - - protected TestInvocationBuilder getAppendAndGet() { - return testDefinitionForVirtualObject( - "GetAppendAndGet", - CoreSerdes.JSON_STRING, - CoreSerdes.JSON_STRING, - (ctx, input) -> { - String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), oldState + input); - - return ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - }); - } - - protected TestInvocationBuilder getClearAndGet() { - return testDefinitionForVirtualObject( - "GetClearAndGet", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, input) -> { - String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - - ctx.clear(StateKey.of("STATE", CoreSerdes.JSON_STRING)); - assertThat(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))).isEmpty(); - return oldState; - }); - } - - protected TestInvocationBuilder getClearAllAndGet() { - return testDefinitionForVirtualObject( - "GetClearAllAndGet", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, input) -> { - String oldState = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - - ctx.clearAll(); - assertThat(ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING))).isEmpty(); - assertThat(ctx.get(StateKey.of("ANOTHER_STATE", CoreSerdes.JSON_STRING))).isEmpty(); - - return oldState; - }); - } - - protected TestInvocationBuilder listKeys() { - return testDefinitionForVirtualObject( - "ListKeys", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, input) -> String.join(",", ctx.stateKeys())); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java b/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java deleted file mode 100644 index 2d315080..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/InvocationIdTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.InvocationIdTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; - -public class InvocationIdTest extends InvocationIdTestSuite { - - protected TestInvocationBuilder returnInvocationId() { - return testDefinitionForService( - "ReturnInvocationId", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> ctx.request().invocationId().toString()); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/JavaBlockingTests.java b/sdk-api/src/test/java/dev/restate/sdk/JavaBlockingTests.java deleted file mode 100644 index 82d0e023..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/JavaBlockingTests.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.core.ProtoUtils.GREETER_SERVICE_TARGET; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.core.MockMultiThreaded; -import dev.restate.sdk.core.MockSingleThread; -import dev.restate.sdk.core.TestDefinitions; -import dev.restate.sdk.core.TestDefinitions.TestExecutor; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import dev.restate.sdk.core.TestDefinitions.TestSuite; -import dev.restate.sdk.core.TestRunner; -import java.util.function.BiFunction; -import java.util.stream.Stream; - -public class JavaBlockingTests extends TestRunner { - - @Override - protected Stream executors() { - return Stream.of(MockSingleThread.INSTANCE, MockMultiThreaded.INSTANCE); - } - - @Override - public Stream definitions() { - return Stream.of( - new AwakeableIdTest(), - new DeferredTest(), - new EagerStateTest(), - new StateTest(), - new InvocationIdTest(), - new OnlyInputAndOutputTest(), - new SideEffectTest(), - new SleepTest(), - new StateMachineFailuresTest(), - new UserFailuresTest(), - new RandomTest()); - } - - public static TestInvocationBuilder testDefinitionForService( - String name, Serde reqSerde, Serde resSerde, BiFunction runner) { - return TestDefinitions.testInvocation( - Service.service(name) - .with(Service.HandlerSignature.of("run", reqSerde, resSerde), runner) - .build(Service.Options.DEFAULT), - "run"); - } - - public static TestInvocationBuilder testDefinitionForVirtualObject( - String name, Serde reqSerde, Serde resSerde, BiFunction runner) { - return TestDefinitions.testInvocation( - Service.virtualObject(name) - .withExclusive(Service.HandlerSignature.of("run", reqSerde, resSerde), runner) - .build(Service.Options.DEFAULT), - "run"); - } - - public static Awaitable callGreeterGreetService(Context ctx, String parameter) { - return ctx.call( - GREETER_SERVICE_TARGET, CoreSerdes.JSON_STRING, CoreSerdes.JSON_STRING, parameter); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java b/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java deleted file mode 100644 index c5a60447..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/OnlyInputAndOutputTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.OnlyInputAndOutputTestSuite; -import dev.restate.sdk.core.TestDefinitions; - -public class OnlyInputAndOutputTest extends OnlyInputAndOutputTestSuite { - - protected TestDefinitions.TestInvocationBuilder noSyscallsGreeter() { - return testDefinitionForService( - "NoSyscallsGreeter", - CoreSerdes.JSON_STRING, - CoreSerdes.JSON_STRING, - (ctx, input) -> "Hello " + input); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java b/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java deleted file mode 100644 index 3a98ac85..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/RandomTest.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.RandomTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.util.Random; - -public class RandomTest extends RandomTestSuite { - - protected TestInvocationBuilder randomShouldBeDeterministic() { - return testDefinitionForService( - "RandomShouldBeDeterministic", - CoreSerdes.VOID, - CoreSerdes.JSON_INT, - (ctx, unused) -> ctx.random().nextInt()); - } - - protected TestInvocationBuilder randomInsideSideEffect() { - return testDefinitionForService( - "RandomInsideSideEffect", - CoreSerdes.VOID, - CoreSerdes.JSON_INT, - (ctx, unused) -> { - ctx.run(() -> ctx.random().nextInt()); - throw new IllegalStateException("This should not unreachable"); - }); - } - - protected int getExpectedInt(long seed) { - return new Random(seed).nextInt(); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java b/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java deleted file mode 100644 index a34c7e6e..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/SideEffectTest.java +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; -import static dev.restate.sdk.core.ProtoUtils.GREETER_SERVICE_TARGET; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.SideEffectTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.util.Objects; - -public class SideEffectTest extends SideEffectTestSuite { - - @Override - protected TestInvocationBuilder sideEffect(String sideEffectOutput) { - return testDefinitionForService( - "SideEffect", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String result = ctx.run(CoreSerdes.JSON_STRING, () -> sideEffectOutput); - return "Hello " + result; - }); - } - - @Override - protected TestInvocationBuilder namedSideEffect(String name, String sideEffectOutput) { - return testDefinitionForService( - "SideEffect", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String result = ctx.run(name, CoreSerdes.JSON_STRING, () -> sideEffectOutput); - return "Hello " + result; - }); - } - - @Override - protected TestInvocationBuilder consecutiveSideEffect(String sideEffectOutput) { - return testDefinitionForService( - "ConsecutiveSideEffect", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String firstResult = ctx.run(CoreSerdes.JSON_STRING, () -> sideEffectOutput); - String secondResult = ctx.run(CoreSerdes.JSON_STRING, firstResult::toUpperCase); - - return "Hello " + secondResult; - }); - } - - @Override - protected TestInvocationBuilder checkContextSwitching() { - return testDefinitionForService( - "CheckContextSwitching", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String currentThread = Thread.currentThread().getName(); - - String sideEffectThread = - ctx.run(CoreSerdes.JSON_STRING, () -> Thread.currentThread().getName()); - - if (!Objects.equals(currentThread, sideEffectThread)) { - throw new IllegalStateException( - "Current thread and side effect thread do not match: " - + currentThread - + " != " - + sideEffectThread); - } - - return "Hello"; - }); - } - - @Override - protected TestInvocationBuilder sideEffectGuard() { - return testDefinitionForService( - "SideEffectGuard", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.run(() -> ctx.send(GREETER_SERVICE_TARGET, new byte[] {})); - throw new IllegalStateException("This point should not be reached"); - }); - } - - @Override - protected TestInvocationBuilder failingSideEffect(String name, String reason) { - return testDefinitionForService( - "FailingSideEffect", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.run( - name, - () -> { - throw new IllegalStateException(reason); - }); - return null; - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java b/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java deleted file mode 100644 index 306bc45e..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/SleepTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.core.SleepTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -public class SleepTest extends SleepTestSuite { - - protected TestInvocationBuilder sleepGreeter() { - return testDefinitionForService( - "SleepGreeter", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.sleep(Duration.ofSeconds(1)); - return "Hello"; - }); - } - - protected TestInvocationBuilder manySleeps() { - return testDefinitionForService( - "ManySleeps", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - List> collectedAwaitables = new ArrayList<>(); - - for (int i = 0; i < 10; i++) { - collectedAwaitables.add(ctx.timer(Duration.ofSeconds(1))); - } - - Awaitable.all( - collectedAwaitables.get(0), - collectedAwaitables.get(1), - collectedAwaitables - .subList(2, collectedAwaitables.size()) - .toArray(Awaitable[]::new)) - .await(); - - return null; - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java b/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java deleted file mode 100644 index f8ea7f7b..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/StateMachineFailuresTest.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForVirtualObject; - -import dev.restate.sdk.common.*; -import dev.restate.sdk.core.StateMachineFailuresTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.atomic.AtomicInteger; - -public class StateMachineFailuresTest extends StateMachineFailuresTestSuite { - - private static final StateKey STATE = - StateKey.of( - "STATE", - Serde.using( - i -> Integer.toString(i).getBytes(StandardCharsets.UTF_8), - b -> Integer.parseInt(new String(b, StandardCharsets.UTF_8)))); - - protected TestInvocationBuilder getState(AtomicInteger nonTerminalExceptionsSeen) { - return testDefinitionForVirtualObject( - "GetState", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - try { - ctx.get(STATE); - } catch (Throwable e) { - // A user should never catch Throwable!!! - if (AbortedExecutionException.INSTANCE.equals(e)) { - AbortedExecutionException.sneakyThrow(); - } - if (!(e instanceof TerminalException)) { - nonTerminalExceptionsSeen.addAndGet(1); - } else { - throw e; - } - } - - return "Francesco"; - }); - } - - protected TestInvocationBuilder sideEffectFailure(Serde serde) { - return testDefinitionForVirtualObject( - "SideEffectFailure", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.run(serde, () -> 0); - return "Francesco"; - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/StateTest.java b/sdk-api/src/test/java/dev/restate/sdk/StateTest.java deleted file mode 100644 index 79ba15cb..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/StateTest.java +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForVirtualObject; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.StateKey; -import dev.restate.sdk.core.StateTestSuite; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; - -public class StateTest extends StateTestSuite { - - protected TestInvocationBuilder getState() { - return testDefinitionForVirtualObject( - "GetState", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - String state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).orElse("Unknown"); - - return "Hello " + state; - }); - } - - protected TestInvocationBuilder getAndSetState() { - return testDefinitionForVirtualObject( - "GetState", - CoreSerdes.JSON_STRING, - CoreSerdes.JSON_STRING, - (ctx, input) -> { - String state = ctx.get(StateKey.of("STATE", CoreSerdes.JSON_STRING)).get(); - - ctx.set(StateKey.of("STATE", CoreSerdes.JSON_STRING), input); - - return "Hello " + state; - }); - } - - protected TestInvocationBuilder setNullState() { - return testDefinitionForVirtualObject( - "GetState", - CoreSerdes.VOID, - CoreSerdes.JSON_STRING, - (ctx, unused) -> { - ctx.set( - StateKey.of( - "STATE", - Serde.using( - l -> { - throw new IllegalStateException("Unexpected call to serde fn"); - }, - l -> { - throw new IllegalStateException("Unexpected call to serde fn"); - })), - null); - - throw new IllegalStateException("set did not fail"); - }); - } -} diff --git a/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java b/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java deleted file mode 100644 index f0acff0d..00000000 --- a/sdk-api/src/test/java/dev/restate/sdk/UserFailuresTest.java +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk; - -import static dev.restate.sdk.JavaBlockingTests.testDefinitionForService; - -import dev.restate.sdk.common.AbortedExecutionException; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.core.TestDefinitions.TestInvocationBuilder; -import dev.restate.sdk.core.UserFailuresTestSuite; -import java.util.concurrent.atomic.AtomicInteger; - -public class UserFailuresTest extends UserFailuresTestSuite { - - protected TestInvocationBuilder throwIllegalStateException() { - return testDefinitionForService( - "ThrowIllegalStateException", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - throw new IllegalStateException("Whatever"); - }); - } - - protected TestInvocationBuilder sideEffectThrowIllegalStateException( - AtomicInteger nonTerminalExceptionsSeen) { - return testDefinitionForService( - "SideEffectThrowIllegalStateException", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - try { - ctx.run( - () -> { - throw new IllegalStateException("Whatever"); - }); - } catch (Throwable e) { - // A user should never catch Throwable!!! - if (AbortedExecutionException.INSTANCE.equals(e)) { - AbortedExecutionException.sneakyThrow(); - } - if (!(e instanceof TerminalException)) { - nonTerminalExceptionsSeen.addAndGet(1); - } - throw e; - } - - throw new IllegalStateException("Unexpected end"); - }); - } - - protected TestInvocationBuilder throwTerminalException(int code, String message) { - return testDefinitionForService( - "ThrowTerminalException", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - throw new TerminalException(code, message); - }); - } - - protected TestInvocationBuilder sideEffectThrowTerminalException(int code, String message) { - return testDefinitionForService( - "SideEffectThrowTerminalException", - CoreSerdes.VOID, - CoreSerdes.VOID, - (ctx, unused) -> { - ctx.run( - () -> { - throw new TerminalException(code, message); - }); - throw new IllegalStateException("This should not be reached"); - }); - } -} diff --git a/sdk-common/build.gradle.kts b/sdk-common/build.gradle.kts deleted file mode 100644 index 65f94437..00000000 --- a/sdk-common/build.gradle.kts +++ /dev/null @@ -1,82 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` -} - -description = "Common interfaces of the Restate SDK" - -dependencies { - compileOnly(coreLibs.jspecify) - - api(coreLibs.protobuf.java) - - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.core) - - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) -} - -val generatedVersionDir = layout.buildDirectory.dir("version") - -generatedVersionDir.get().asFile.mkdirs() - -sourceSets { main { java { srcDir(generatedVersionDir) } } } - -// Configure generation of version class - -// From https://discuss.kotlinlang.org/t/use-git-hash-as-version-number-in-build-gradle-kts/19818/4 -fun String.runCommand( - workingDir: File = File("."), - timeoutAmount: Long = 5, - timeoutUnit: TimeUnit = TimeUnit.SECONDS -): String = - ProcessBuilder(split("\\s(?=(?:[^'\"`]*(['\"`])[^'\"`]*\\1)*[^'\"`]*$)".toRegex())) - .directory(workingDir) - .redirectOutput(ProcessBuilder.Redirect.PIPE) - .redirectError(ProcessBuilder.Redirect.PIPE) - .start() - .apply { waitFor(timeoutAmount, timeoutUnit) } - .run { - val error = errorStream.bufferedReader().readText().trim() - if (error.isNotEmpty()) { - throw IllegalStateException(error) - } - inputStream.bufferedReader().readText().trim() - } - -val generateVersionClass = - tasks.register("generateVersionClass") { - dependsOn(project.tasks.processResources) - outputs.dir(generatedVersionDir) - - doFirst { - val gitHash = "git rev-parse --short=8 HEAD".runCommand(workingDir = rootDir) - val containingDir = generatedVersionDir.get().dir("dev/restate/sdk/version").asFile - assert(containingDir.exists() || containingDir.mkdirs()) - - file("$containingDir/Version.java") - .writeText( - """ - package dev.restate.sdk.version; - - public final class Version { - private Version() {} - - public static final String VERSION = "$version"; - public static final String GIT_HASH = "$gitHash"; - public static final String X_RESTATE_SERVER = "restate-sdk-java/" + VERSION + "_" + GIT_HASH; - } - """ - .trimIndent()) - - check(file("${projectDir}/build/version/dev/restate/sdk/version/Version.java").exists()) { - "${projectDir}/build/version/dev/restate/sdk/version/Version.java doesn't exist?!" - } - } - } - -tasks { - withType().configureEach { dependsOn(generateVersionClass) } - withType().configureEach { dependsOn(generateVersionClass) } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java deleted file mode 100644 index 6fcecfd9..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Exclusive.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -public @interface Exclusive {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java deleted file mode 100644 index feb2b8b7..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Handler.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -public @interface Handler {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java deleted file mode 100644 index 8563d584..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Service.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) -public @interface Service { - /** - * Name of the Service for Restate. If not provided, it will be the FQCN of the annotated element. - */ - String name() default ""; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java deleted file mode 100644 index 3e205012..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Shared.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -public @interface Shared {} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java deleted file mode 100644 index 0af3e843..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/VirtualObject.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) -public @interface VirtualObject { - /** - * Name of the VirtualObject for Restate. If not provided, it will be the FQCN of the annotated - * element. - */ - String name() default ""; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java b/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java deleted file mode 100644 index 0538292c..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/annotation/Workflow.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.SOURCE) -public @interface Workflow { - /** - * Name of the Workflow for Restate. If not provided, it will be the FQCN of the annotated - * element. - */ - String name() default ""; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/client/DefaultIngressClient.java b/sdk-common/src/main/java/dev/restate/sdk/client/DefaultIngressClient.java deleted file mode 100644 index ebff7dc9..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/client/DefaultIngressClient.java +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.client; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.Target; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import org.jspecify.annotations.NonNull; - -public class DefaultIngressClient implements IngressClient { - - private static final JsonFactory JSON_FACTORY = new JsonFactory(); - - private final HttpClient httpClient; - private final URI baseUri; - private final Map headers; - - public DefaultIngressClient(HttpClient httpClient, String baseUri, Map headers) { - this.httpClient = httpClient; - this.baseUri = URI.create(baseUri); - this.headers = headers; - } - - @Override - public CompletableFuture callAsync( - Target target, - Serde reqSerde, - Serde resSerde, - Req req, - RequestOptions requestOptions) { - HttpRequest request = prepareHttpRequest(target, false, reqSerde, req, null, requestOptions); - return httpClient - .sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()) - .handle( - (response, throwable) -> { - if (throwable != null) { - throw new IngressException("Error when executing the request", throwable); - } - - if (response.statusCode() >= 300) { - handleNonSuccessResponse(response); - } - - try { - return resSerde.deserialize(response.body()); - } catch (Exception e) { - throw new IngressException( - "Cannot deserialize the response", response.statusCode(), response.body(), e); - } - }); - } - - @Override - public CompletableFuture sendAsync( - Target target, Serde reqSerde, Req req, Duration delay, RequestOptions options) { - HttpRequest request = prepareHttpRequest(target, true, reqSerde, req, delay, options); - return httpClient - .sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()) - .handle( - (response, throwable) -> { - if (throwable != null) { - throw new IngressException("Error when executing the request", throwable); - } - - if (response.statusCode() >= 300) { - handleNonSuccessResponse(response); - } - - try { - return findStringFieldInJsonObject( - new ByteArrayInputStream(response.body()), "invocationId"); - } catch (Exception e) { - throw new IngressException( - "Cannot deserialize the response", response.statusCode(), response.body(), e); - } - }); - } - - @Override - public AwakeableHandle awakeableHandle(String id) { - return new AwakeableHandle() { - @Override - public CompletableFuture resolveAsync(Serde serde, @NonNull T payload) { - // Prepare request - var reqBuilder = - HttpRequest.newBuilder().uri(baseUri.resolve("/restate/awakeables/" + id + "/resolve")); - - // Add content-type - if (serde.contentType() != null) { - reqBuilder.header("content-type", serde.contentType()); - } - - // Add headers - headers.forEach(reqBuilder::header); - - // Build and Send request - HttpRequest request = - reqBuilder - .POST(HttpRequest.BodyPublishers.ofByteArray(serde.serialize(payload))) - .build(); - return httpClient - .sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()) - .handle( - (response, throwable) -> { - if (throwable != null) { - throw new IngressException("Error when executing the request", throwable); - } - - if (response.statusCode() >= 300) { - handleNonSuccessResponse(response); - } - - return null; - }); - } - - @Override - public CompletableFuture rejectAsync(String reason) { - // Prepare request - var reqBuilder = - HttpRequest.newBuilder() - .uri(baseUri.resolve("/restate/awakeables/" + id + "/reject")) - .header("content-type", "text-plain"); - - // Add headers - headers.forEach(reqBuilder::header); - - // Build and Send request - HttpRequest request = reqBuilder.POST(HttpRequest.BodyPublishers.ofString(reason)).build(); - return httpClient - .sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()) - .handle( - (response, throwable) -> { - if (throwable != null) { - throw new IngressException("Error when executing the request", throwable); - } - - if (response.statusCode() >= 300) { - handleNonSuccessResponse(response); - } - - return null; - }); - } - }; - } - - private URI toRequestURI(Target target, boolean isSend, Duration delay) { - StringBuilder builder = new StringBuilder(); - builder.append("/").append(target.getService()); - if (target.getKey() != null) { - builder.append("/").append(target.getKey()); - } - builder.append("/").append(target.getHandler()); - if (isSend) { - builder.append("/send"); - } - if (delay != null && !delay.isZero() && !delay.isNegative()) { - builder.append("?delay=").append(delay); - } - - return this.baseUri.resolve(builder.toString()); - } - - private HttpRequest prepareHttpRequest( - Target target, - boolean isSend, - Serde reqSerde, - Req req, - Duration delay, - RequestOptions options) { - var reqBuilder = HttpRequest.newBuilder().uri(toRequestURI(target, isSend, delay)); - - // Add content-type - if (reqSerde.contentType() != null) { - reqBuilder.header("content-type", reqSerde.contentType()); - } - - // Add headers - this.headers.forEach(reqBuilder::header); - - // Add idempotency key and period - if (options.getIdempotencyKey() != null) { - reqBuilder.header("idempotency-key", options.getIdempotencyKey()); - } - - // Add additional headers - options.getAdditionalHeaders().forEach(reqBuilder::header); - - return reqBuilder.POST(HttpRequest.BodyPublishers.ofByteArray(reqSerde.serialize(req))).build(); - } - - private void handleNonSuccessResponse(HttpResponse response) { - if (response.headers().firstValue("content-type").orElse("").contains("application/json")) { - String errorMessage; - // Let's try to parse the message field - try { - errorMessage = - findStringFieldInJsonObject(new ByteArrayInputStream(response.body()), "message"); - } catch (Exception e) { - throw new IngressException( - "Can't decode error response from ingress", response.statusCode(), response.body(), e); - } - throw new IngressException(errorMessage, response.statusCode(), response.body()); - } - - // Fallback error - throw new IngressException( - "Received non success status code", response.statusCode(), response.body()); - } - - private static String findStringFieldInJsonObject(InputStream body, String fieldName) - throws IOException { - try (JsonParser parser = JSON_FACTORY.createParser(body)) { - if (parser.nextToken() != JsonToken.START_OBJECT) { - throw new IllegalStateException( - "Expecting token " + JsonToken.START_OBJECT + ", got " + parser.getCurrentToken()); - } - for (String actualFieldName = parser.nextFieldName(); - actualFieldName != null; - actualFieldName = parser.nextFieldName()) { - if (actualFieldName.equalsIgnoreCase(fieldName)) { - return parser.nextTextValue(); - } else { - parser.nextValue(); - } - } - throw new IllegalStateException( - "Expecting field name \"" + fieldName + "\", got " + parser.getCurrentToken()); - } - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/client/IngressClient.java b/sdk-common/src/main/java/dev/restate/sdk/client/IngressClient.java deleted file mode 100644 index a82510b1..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/client/IngressClient.java +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.client; - -import dev.restate.sdk.common.Serde; -import dev.restate.sdk.common.Target; -import java.net.http.HttpClient; -import java.time.Duration; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -public interface IngressClient { - - CompletableFuture callAsync( - Target target, Serde reqSerde, Serde resSerde, Req req, RequestOptions options); - - default CompletableFuture callAsync( - Target target, Serde reqSerde, Serde resSerde, Req req) { - return callAsync(target, reqSerde, resSerde, req, RequestOptions.DEFAULT); - } - - default Res call( - Target target, Serde reqSerde, Serde resSerde, Req req, RequestOptions options) - throws IngressException { - try { - return callAsync(target, reqSerde, resSerde, req, options).join(); - } catch (CompletionException e) { - if (e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); - } - throw new RuntimeException(e.getCause()); - } - } - - default Res call(Target target, Serde reqSerde, Serde resSerde, Req req) - throws IngressException { - return call(target, reqSerde, resSerde, req, RequestOptions.DEFAULT); - } - - CompletableFuture sendAsync( - Target target, - Serde reqSerde, - Req req, - @Nullable Duration delay, - RequestOptions options); - - default CompletableFuture sendAsync( - Target target, Serde reqSerde, Req req, @Nullable Duration delay) { - return sendAsync(target, reqSerde, req, delay, RequestOptions.DEFAULT); - } - - default CompletableFuture sendAsync(Target target, Serde reqSerde, Req req) { - return sendAsync(target, reqSerde, req, null, RequestOptions.DEFAULT); - } - - default String send( - Target target, Serde reqSerde, Req req, @Nullable Duration delay, RequestOptions options) - throws IngressException { - try { - return sendAsync(target, reqSerde, req, delay, options).join(); - } catch (CompletionException e) { - if (e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); - } - throw new RuntimeException(e.getCause()); - } - } - - default String send(Target target, Serde reqSerde, Req req, @Nullable Duration delay) - throws IngressException { - return send(target, reqSerde, req, delay, RequestOptions.DEFAULT); - } - - default String send(Target target, Serde reqSerde, Req req) throws IngressException { - return send(target, reqSerde, req, null, RequestOptions.DEFAULT); - } - - /** - * Create a new {@link AwakeableHandle} for the provided identifier. You can use it to {@link - * AwakeableHandle#resolve(Serde, Object)} or {@link AwakeableHandle#reject(String)} an Awakeable - * from the ingress. - */ - AwakeableHandle awakeableHandle(String id); - - /** - * This class represents a handle to an Awakeable. It can be used to complete awakeables from the - * ingress - */ - interface AwakeableHandle { - /** Same as {@link #resolve(Serde, Object)} but async. */ - CompletableFuture resolveAsync(Serde serde, @NonNull T payload); - - /** - * Complete with success the Awakeable. - * - * @param serde used to serialize the Awakeable result payload. - * @param payload the result payload. MUST NOT be null. - */ - default void resolve(Serde serde, @NonNull T payload) { - try { - resolveAsync(serde, payload).join(); - } catch (CompletionException e) { - if (e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); - } - throw new RuntimeException(e.getCause()); - } - } - - /** Same as {@link #reject(String)} but async. */ - CompletableFuture rejectAsync(String reason); - - /** - * Complete with failure the Awakeable. - * - * @param reason the rejection reason. MUST NOT be null. - */ - default void reject(String reason) { - try { - rejectAsync(reason).join(); - } catch (CompletionException e) { - if (e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); - } - throw new RuntimeException(e.getCause()); - } - } - } - - static IngressClient defaultClient(String baseUri) { - return defaultClient(baseUri, Collections.emptyMap()); - } - - static IngressClient defaultClient(String baseUri, Map headers) { - return new DefaultIngressClient(HttpClient.newHttpClient(), baseUri, headers); - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/client/IngressException.java b/sdk-common/src/main/java/dev/restate/sdk/client/IngressException.java deleted file mode 100644 index 4a134ad8..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/client/IngressException.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.client; - -import java.nio.charset.StandardCharsets; -import org.jspecify.annotations.Nullable; - -public class IngressException extends RuntimeException { - - private final int statusCode; - private final byte[] responseBody; - - public IngressException(String message, Throwable cause) { - this(message, -1, null, cause); - } - - public IngressException(String message, int statusCode, byte[] responseBody) { - this(message, statusCode, responseBody, null); - } - - public IngressException(String message, int statusCode, byte[] responseBody, Throwable cause) { - super(message, cause); - this.statusCode = statusCode; - this.responseBody = responseBody; - } - - public int getStatusCode() { - return statusCode; - } - - public byte @Nullable [] getResponseBody() { - return responseBody; - } - - @Override - public String toString() { - return "IngressException{" - + "statusCode=" - + statusCode - + ", responseBody='" - + new String(responseBody, StandardCharsets.UTF_8) - + '\'' - + ", message='" - + this.getMessage() - + '\'' - + '}'; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/client/RequestOptions.java b/sdk-common/src/main/java/dev/restate/sdk/client/RequestOptions.java deleted file mode 100644 index 62f4679a..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/client/RequestOptions.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.client; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class RequestOptions { - - public static final RequestOptions DEFAULT = new RequestOptions(); - - private String idempotencyKey; - private final Map additionalHeaders = new HashMap<>(); - - public RequestOptions withIdempotency(String idempotencyKey) { - this.idempotencyKey = idempotencyKey; - return this; - } - - public RequestOptions withHeader(String name, String value) { - this.additionalHeaders.put(name, value); - return this; - } - - public RequestOptions withHeaders(Map additionalHeaders) { - this.additionalHeaders.putAll(additionalHeaders); - return this; - } - - public String getIdempotencyKey() { - return idempotencyKey; - } - - public Map getAdditionalHeaders() { - return additionalHeaders; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - RequestOptions that = (RequestOptions) o; - - if (!Objects.equals(idempotencyKey, that.idempotencyKey)) return false; - return additionalHeaders.equals(that.additionalHeaders); - } - - @Override - public int hashCode() { - int result = idempotencyKey != null ? idempotencyKey.hashCode() : 0; - result = 31 * result + additionalHeaders.hashCode(); - return result; - } - - @Override - public String toString() { - return "RequestOptions{" - + "idempotencyKey='" - + idempotencyKey - + '\'' - + ", additionalHeaders=" - + additionalHeaders - + '}'; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/AbortedExecutionException.java b/sdk-common/src/main/java/dev/restate/sdk/common/AbortedExecutionException.java deleted file mode 100644 index 80167f3d..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/AbortedExecutionException.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -/** You MUST NOT catch this exception. */ -public final class AbortedExecutionException extends Throwable { - @SuppressWarnings("StaticAssignmentOfThrowable") - public static final AbortedExecutionException INSTANCE = new AbortedExecutionException(); - - @SuppressWarnings("unchecked") - public static void sneakyThrow() throws E { - throw (E) AbortedExecutionException.INSTANCE; - } - - private AbortedExecutionException() { - super("AbortedExecutionException"); - setStackTrace(new StackTraceElement[] {}); - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/BindableService.java b/sdk-common/src/main/java/dev/restate/sdk/common/BindableService.java deleted file mode 100644 index e2798304..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/BindableService.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import dev.restate.sdk.common.syscalls.ServiceDefinition; -import java.util.List; - -/** Definition of bindable Restate service. */ -public interface BindableService { - - O options(); - - List> definitions(); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/BindableServiceFactory.java b/sdk-common/src/main/java/dev/restate/sdk/common/BindableServiceFactory.java deleted file mode 100644 index b6260d5b..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/BindableServiceFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -public interface BindableServiceFactory { - - BindableService create(T serviceObject); - - boolean supports(Object serviceObject); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/CoreSerdes.java b/sdk-common/src/main/java/dev/restate/sdk/common/CoreSerdes.java deleted file mode 100644 index 6158ec0d..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/CoreSerdes.java +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.function.ThrowingBiConsumer; -import dev.restate.sdk.common.function.ThrowingFunction; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Objects; -import org.jspecify.annotations.Nullable; - -/** - * Collection of common serializers/deserializers. - * - *

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

You can embed it in external system requests by using {@link #toString()}. - */ -public interface InvocationId { - - /** - * @return a seed to be used with {@link java.util.Random}. - */ - long toRandomSeed(); - - @Override - String toString(); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Request.java b/sdk-common/src/main/java/dev/restate/sdk/common/Request.java deleted file mode 100644 index 0b297384..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/Request.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import com.google.protobuf.ByteString; -import java.util.Map; -import java.util.Objects; - -public final class Request { - - private final InvocationId invocationId; - private final ByteString body; - private final Map headers; - - public Request(InvocationId invocationId, ByteString body, Map headers) { - this.invocationId = invocationId; - this.body = body; - this.headers = headers; - } - - public InvocationId invocationId() { - return invocationId; - } - - public byte[] body() { - return body.toByteArray(); - } - - public ByteString bodyBuffer() { - return body; - } - - public Map headers() { - return headers; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Request request = (Request) o; - - if (!Objects.equals(invocationId, request.invocationId)) return false; - if (!Objects.equals(body, request.body)) return false; - return Objects.equals(headers, request.headers); - } - - @Override - public int hashCode() { - int result = invocationId != null ? invocationId.hashCode() : 0; - result = 31 * result + (body != null ? body.hashCode() : 0); - result = 31 * result + (headers != null ? headers.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "Request{" - + "invocationId=" - + invocationId - + ", body=" - + body - + ", headers=" - + headers - + '}'; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java b/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java deleted file mode 100644 index df74ebcd..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/Serde.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import com.google.protobuf.ByteString; -import com.google.protobuf.UnsafeByteOperations; -import dev.restate.sdk.common.function.ThrowingFunction; -import java.util.Objects; -import org.jspecify.annotations.Nullable; - -/** Interface defining serialization and deserialization of concrete types. */ -public interface Serde { - - byte[] serialize(@Nullable T value); - - default ByteString serializeToByteString(@Nullable T value) { - // This is safe because we don't mutate the generated byte[] afterward. - return UnsafeByteOperations.unsafeWrap(serialize(value)); - } - - T deserialize(byte[] value); - - default T deserialize(ByteString byteString) { - return deserialize(byteString.toByteArray()); - } - - /** - * @return the schema of this object. - */ - default @Nullable Object schema() { - return null; - } - - default @Nullable String contentType() { - return null; - } - - /** - * Create a {@link Serde} from {@code serializer}/{@code deserializer} lambdas. Before invoking - * the serializer, we check that {@code value} is non-null. - */ - static Serde using( - ThrowingFunction serializer, ThrowingFunction deserializer) { - return new Serde<>() { - @Override - public byte[] serialize(T value) { - return serializer.asFunction().apply(Objects.requireNonNull(value)); - } - - @Override - public T deserialize(byte[] value) { - return deserializer.asFunction().apply(value); - } - }; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/ServiceType.java b/sdk-common/src/main/java/dev/restate/sdk/common/ServiceType.java deleted file mode 100644 index 10a9b776..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/ServiceType.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -public enum ServiceType { - SERVICE, - VIRTUAL_OBJECT, - WORKFLOW -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/StateKey.java b/sdk-common/src/main/java/dev/restate/sdk/common/StateKey.java deleted file mode 100644 index bc54cf0e..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/StateKey.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -/** - * This class holds information about state's name and its type tag to be used for serializing and - * deserializing it. - * - * @param the generic type of the state. - */ -public final class StateKey { - - private final String name; - private final Serde serde; - - private StateKey(String name, Serde serde) { - this.name = name; - this.serde = serde; - } - - /** Create a new {@link StateKey}. */ - public static StateKey of(String name, Serde serde) { - return new StateKey<>(name, serde); - } - - /** Create a new {@link StateKey} for {@link String} state. */ - public static StateKey string(String name) { - return new StateKey<>(name, CoreSerdes.JSON_STRING); - } - - /** Create a new {@link StateKey} for bytes state. */ - public static StateKey raw(String name) { - return new StateKey<>(name, CoreSerdes.RAW); - } - - public String name() { - return name; - } - - public Serde serde() { - return serde; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/Target.java b/sdk-common/src/main/java/dev/restate/sdk/common/Target.java deleted file mode 100644 index f118613d..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/Target.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -import java.util.Objects; - -public final class Target { - - private final String service; - private final String handler; - private final String key; - - private Target(String service, String handler, String key) { - this.service = service; - this.handler = handler; - this.key = key; - } - - public static Target virtualObject(String name, String key, String handler) { - return new Target(name, handler, key); - } - - public static Target service(String name, String handler) { - return new Target(name, handler, null); - } - - public String getService() { - return service; - } - - public String getHandler() { - return handler; - } - - public String getKey() { - return key; - } - - @Override - public boolean equals(Object object) { - if (this == object) return true; - if (object == null || getClass() != object.getClass()) return false; - Target target = (Target) object; - return Objects.equals(service, target.service) - && Objects.equals(handler, target.handler) - && Objects.equals(key, target.key); - } - - @Override - public int hashCode() { - return Objects.hash(service, handler, key); - } - - @Override - public String toString() { - if (key == null) { - return service + "/" + handler; - } - return service + "/" + key + "/" + handler; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/TerminalException.java b/sdk-common/src/main/java/dev/restate/sdk/common/TerminalException.java deleted file mode 100644 index 7dd5a735..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/TerminalException.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common; - -/** When thrown in a Restate service method, it will complete the invocation with an error. */ -public class TerminalException extends RuntimeException { - - public static final int ABORTED_CODE = 409; - public static final int CANCELLED_CODE = ABORTED_CODE; - public static final int BAD_REQUEST_CODE = 400; - public static final int INTERNAL_SERVER_ERROR_CODE = 500; - - private final int code; - - public TerminalException() { - this.code = INTERNAL_SERVER_ERROR_CODE; - } - - /** Like {@link #TerminalException(int, String)}, without message. */ - public TerminalException(int code) { - this.code = code; - } - - /** - * Create a new {@link TerminalException}. - * - * @param code HTTP response status code - * @param message error message - */ - public TerminalException(int code, String message) { - super(message); - this.code = code; - } - - /** - * Like {@link #TerminalException(int, String)}, with code {@link #INTERNAL_SERVER_ERROR_CODE}. - */ - public TerminalException(String message) { - super(message); - this.code = INTERNAL_SERVER_ERROR_CODE; - } - - /** - * @return status code - */ - public int getCode() { - return code; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingBiConsumer.java b/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingBiConsumer.java deleted file mode 100644 index 30bade48..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingBiConsumer.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.function; - -import java.util.function.BiConsumer; - -/** Like {@link BiConsumer} but can throw checked exceptions. */ -@FunctionalInterface -public interface ThrowingBiConsumer { - void accept(T var1, U var2) throws Throwable; - - static BiConsumer wrap(ThrowingBiConsumer fn) { - return fn.asBiConsumer(); - } - - default BiConsumer asBiConsumer() { - return (t, u) -> { - try { - this.accept(t, u); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } catch (Throwable e) { - // Make sure we propagate java.lang.Error - sneakyThrow(e); - } - }; - } - - @SuppressWarnings("unchecked") - private static void sneakyThrow(Throwable e) throws E { - throw (E) e; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingFunction.java b/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingFunction.java deleted file mode 100644 index 032adeac..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingFunction.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.function; - -import java.util.function.Function; - -/** Like {@link java.util.function.Function} but can throw checked exceptions. */ -@FunctionalInterface -public interface ThrowingFunction { - R apply(T var1) throws Throwable; - - static Function wrap(ThrowingFunction fn) { - return fn.asFunction(); - } - - default Function asFunction() { - return t -> { - try { - return this.apply(t); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } catch (Throwable e) { - // Make sure we propagate java.lang.Error - sneakyThrow(e); - return null; - } - }; - } - - @SuppressWarnings("unchecked") - private static void sneakyThrow(Throwable e) throws E { - throw (E) e; - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingRunnable.java b/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingRunnable.java deleted file mode 100644 index 8f95b42c..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingRunnable.java +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.function; - -/** Like {@link Runnable} but can throw checked exceptions. */ -@FunctionalInterface -public interface ThrowingRunnable { - - /** Run, potentially throwing an exception. */ - void run() throws Throwable; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingSupplier.java b/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingSupplier.java deleted file mode 100644 index 775bc5d0..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/function/ThrowingSupplier.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.function; - -/** Like {@link java.util.function.Supplier} but can throw checked exceptions. */ -@FunctionalInterface -public interface ThrowingSupplier { - - /** - * Get a result, potentially throwing an exception. - * - * @return a result - */ - T get() throws Throwable; -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Deferred.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Deferred.java deleted file mode 100644 index 9b93148f..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Deferred.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import org.jspecify.annotations.Nullable; - -/** - * Interface to define interaction with deferred results. - * - *

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

To resolve a {@link Deferred}, use {@link Syscalls#resolveDeferred(Deferred, SyscallCallback)} - */ -public interface Deferred { - - boolean isCompleted(); - - /** - * @return {@code null} if {@link #isCompleted()} is false. - */ - @Nullable Result toResult(); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/EnterSideEffectSyscallCallback.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/EnterSideEffectSyscallCallback.java deleted file mode 100644 index 9559e0b4..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/EnterSideEffectSyscallCallback.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -public interface EnterSideEffectSyscallCallback extends ExitSideEffectSyscallCallback { - - void onNotExecuted(); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExitSideEffectSyscallCallback.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExitSideEffectSyscallCallback.java deleted file mode 100644 index 1f590a37..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/ExitSideEffectSyscallCallback.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.TerminalException; - -public interface ExitSideEffectSyscallCallback extends SyscallCallback { - - /** This is user failure. */ - void onFailure(TerminalException t); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/HandlerDefinition.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/HandlerDefinition.java deleted file mode 100644 index 64287eb5..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/HandlerDefinition.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import dev.restate.sdk.common.HandlerType; -import java.util.Objects; - -public final class HandlerDefinition { - private final String name; - private final HandlerType handlerType; - private final Object inputSchema; - private final Object outputSchema; - private final InvocationHandler handler; - - public HandlerDefinition( - String name, - HandlerType handlerType, - Object inputSchema, - Object outputSchema, - InvocationHandler handler) { - this.name = name; - this.handlerType = handlerType; - this.inputSchema = inputSchema; - this.outputSchema = outputSchema; - this.handler = handler; - } - - public String getName() { - return name; - } - - public HandlerType getHandlerType() { - return handlerType; - } - - public Object getInputSchema() { - return inputSchema; - } - - public Object getOutputSchema() { - return outputSchema; - } - - public InvocationHandler getHandler() { - return handler; - } - - @Override - public boolean equals(Object object) { - if (this == object) return true; - if (object == null || getClass() != object.getClass()) return false; - HandlerDefinition that = (HandlerDefinition) object; - return Objects.equals(name, that.name) - && Objects.equals(inputSchema, that.inputSchema) - && Objects.equals(outputSchema, that.outputSchema) - && Objects.equals(handler, that.handler); - } - - @Override - public int hashCode() { - return Objects.hash(name, inputSchema, outputSchema, handler); - } -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java deleted file mode 100644 index 2a141fdc..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/InvocationHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import com.google.protobuf.ByteString; - -public interface InvocationHandler { - /** - * Thread local to store {@link Syscalls}. - * - *

Implementations of {@link InvocationHandler} should correctly propagate this thread local in - * order for logging to work correctly. Could be improved if ScopedContext will ever be introduced in - * log4j2. - */ - ThreadLocal SYSCALLS_THREAD_LOCAL = new ThreadLocal<>(); - - void handle(Syscalls syscalls, O options, SyscallCallback callback); -} diff --git a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Result.java b/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Result.java deleted file mode 100644 index 7f6a774e..00000000 --- a/sdk-common/src/main/java/dev/restate/sdk/common/syscalls/Result.java +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.common.syscalls; - -import dev.restate.sdk.common.TerminalException; -import java.util.function.Function; -import org.jspecify.annotations.Nullable; - -/** - * Result can be 3 valued: - * - *

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

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

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

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

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

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

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

There are some special cases: - * - *

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

You MUST first wire up the subscriber and publisher returned by {@link #input()} and {@link - * #output()} and then {@link #start()} the invocation. - */ -public interface ResolvedEndpointHandler { - - InvocationFlow.InvocationInputSubscriber input(); - - InvocationFlow.InvocationOutputPublisher output(); - - void start(); -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandlerImpl.java b/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandlerImpl.java deleted file mode 100644 index 8c6690c9..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ResolvedEndpointHandlerImpl.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import com.google.protobuf.ByteString; -import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.common.syscalls.InvocationHandler; -import dev.restate.sdk.common.syscalls.SyscallCallback; -import dev.restate.sdk.common.syscalls.Syscalls; -import java.util.concurrent.Executor; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jspecify.annotations.Nullable; - -final class ResolvedEndpointHandlerImpl implements ResolvedEndpointHandler { - - private static final Logger LOG = LogManager.getLogger(ResolvedEndpointHandlerImpl.class); - - private final InvocationStateMachine stateMachine; - private final InvocationHandler wrappedHandler; - private final Object componentOptions; - private final @Nullable Executor syscallsExecutor; - - public ResolvedEndpointHandlerImpl( - InvocationStateMachine stateMachine, - InvocationHandler handler, - Object serviceOptions, - @Nullable Executor syscallExecutor) { - this.stateMachine = stateMachine; - this.wrappedHandler = new InvocationHandlerWrapper<>(handler); - this.componentOptions = serviceOptions; - this.syscallsExecutor = syscallExecutor; - } - - @Override - public InvocationFlow.InvocationInputSubscriber input() { - return new ExceptionCatchingInvocationInputSubscriber(stateMachine); - } - - @Override - public InvocationFlow.InvocationOutputPublisher output() { - return stateMachine; - } - - @Override - public void start() { - LOG.info("Start processing invocation"); - stateMachine.startAndConsumeInput( - SyscallCallback.of( - request -> { - // Prepare Syscalls object - SyscallsInternal syscalls = - this.syscallsExecutor != null - ? new ExecutorSwitchingSyscalls( - new SyscallsImpl(request, stateMachine), this.syscallsExecutor) - : new SyscallsImpl(request, stateMachine); - - // pollInput then invoke the wrappedHandler - wrappedHandler.handle( - syscalls, - componentOptions, - SyscallCallback.of( - o -> this.writeOutputAndEnd(syscalls, o), t -> this.end(syscalls, t))); - }, - t -> {})); - } - - private void writeOutputAndEnd(SyscallsInternal syscalls, ByteString output) { - syscalls.writeOutput( - output, - SyscallCallback.ofVoid( - () -> { - LOG.trace("Wrote output message:\n{}", output); - this.end(syscalls, null); - }, - syscalls::fail)); - } - - private void end(SyscallsInternal syscalls, @Nullable Throwable exception) { - if (exception == null || Util.containsSuspendedException(exception)) { - syscalls.close(); - } else { - LOG.warn("Error when processing the invocation", exception); - if (Util.isTerminalException(exception)) { - syscalls.writeOutput( - (TerminalException) exception, - SyscallCallback.ofVoid( - () -> { - LOG.trace("Closed correctly with non ok exception", exception); - syscalls.close(); - }, - syscalls::fail)); - } else { - syscalls.fail(exception); - } - } - } - - private static class InvocationHandlerWrapper implements InvocationHandler { - - private final InvocationHandler handler; - - private InvocationHandlerWrapper(InvocationHandler handler) { - this.handler = handler; - } - - @Override - public void handle(Syscalls syscalls, O options, SyscallCallback callback) { - try { - this.handler.handle(syscalls, options, callback); - } catch (Throwable e) { - callback.onCancel(e); - } - } - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/RestateContextDataProvider.java b/sdk-core/src/main/java/dev/restate/sdk/core/RestateContextDataProvider.java deleted file mode 100644 index 8dc7cce0..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/RestateContextDataProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import dev.restate.sdk.common.syscalls.InvocationHandler; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.apache.logging.log4j.core.util.ContextDataProvider; - -/** - * Log4j2 {@link ContextDataProvider} inferring context from {@link - * InvocationHandler#SYSCALLS_THREAD_LOCAL}. - * - *

This is used to propagate the context to the user code, such that log statements from the user - * will contain the restate logging context variables. - */ -public class RestateContextDataProvider implements ContextDataProvider { - @Override - public Map supplyContextData() { - SyscallsInternal syscalls = (SyscallsInternal) InvocationHandler.SYSCALLS_THREAD_LOCAL.get(); - if (syscalls == null) { - return Collections.emptyMap(); - } - - // We can't use the immutable MapN implementation from Map.of because of - // https://github.com/apache/logging-log4j2/issues/2098 - HashMap m = new HashMap<>(3); - m.put( - RestateEndpoint.LoggingContextSetter.INVOCATION_ID_KEY, - syscalls.request().invocationId().toString()); - m.put( - RestateEndpoint.LoggingContextSetter.INVOCATION_TARGET_KEY, - syscalls.getFullyQualifiedMethodName()); - m.put( - RestateEndpoint.LoggingContextSetter.INVOCATION_STATUS_KEY, - syscalls.getInvocationState().toString()); - return m; - } -} diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java b/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java deleted file mode 100644 index 54a67063..00000000 --- a/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.core; - -import dev.restate.sdk.common.BindableServiceFactory; -import dev.restate.sdk.common.syscalls.HandlerDefinition; -import dev.restate.sdk.common.syscalls.ServiceDefinition; -import dev.restate.sdk.core.manifest.Component; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.*; -import java.util.concurrent.Executor; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; -import org.jspecify.annotations.Nullable; - -public class RestateEndpoint { - - private static final Logger LOG = LogManager.getLogger(RestateEndpoint.class); - - private final Map> services; - private final Tracer tracer; - private final DeploymentManifest deploymentManifest; - - private RestateEndpoint( - DeploymentManifestSchema.ProtocolMode protocolMode, - Map> services, - Tracer tracer) { - this.services = services; - this.tracer = tracer; - this.deploymentManifest = - new DeploymentManifest(protocolMode, services.values().stream().map(c -> c.service)); - - this.logCreation(); - } - - public ResolvedEndpointHandler resolve( - String componentName, - String handlerName, - io.opentelemetry.context.Context otelContext, - LoggingContextSetter loggingContextSetter, - @Nullable Executor syscallExecutor) - throws ProtocolException { - // Resolve the service method definition - @SuppressWarnings("unchecked") - ServiceAndOptions svc = (ServiceAndOptions) this.services.get(componentName); - if (svc == null) { - throw ProtocolException.methodNotFound(componentName, handlerName); - } - String fullyQualifiedServiceMethod = componentName + "/" + handlerName; - HandlerDefinition handler = svc.service.getHandler(handlerName); - if (handler == null) { - throw ProtocolException.methodNotFound(componentName, handlerName); - } - - // Generate the span - Span span = - tracer - .spanBuilder("Invoke method") - .setSpanKind(SpanKind.SERVER) - .setParent(otelContext) - .setAttribute(SemanticAttributes.RPC_SYSTEM, "restate") - .setAttribute(SemanticAttributes.RPC_SERVICE, componentName) - .setAttribute(SemanticAttributes.RPC_METHOD, handlerName) - .startSpan(); - - // Setup logging context - loggingContextSetter.set( - LoggingContextSetter.INVOCATION_TARGET_KEY, fullyQualifiedServiceMethod); - - // Instantiate state machine, syscall and grpc bridge - InvocationStateMachine stateMachine = - new InvocationStateMachine( - componentName, fullyQualifiedServiceMethod, span, loggingContextSetter); - - return new ResolvedEndpointHandlerImpl( - stateMachine, handler.getHandler(), svc.options, syscallExecutor); - } - - public DeploymentManifestSchema handleDiscoveryRequest() { - DeploymentManifestSchema response = this.deploymentManifest.manifest(); - LOG.info( - "Replying to discovery request with services [{}]", - response.getComponents().stream() - .map(Component::getFullyQualifiedComponentName) - .collect(Collectors.joining(","))); - return response; - } - - private void logCreation() { - LOG.info("Registered services: {}", this.services.keySet()); - } - - // -- Builder - - public static Builder newBuilder(DeploymentManifestSchema.ProtocolMode protocolMode) { - return new Builder(protocolMode); - } - - public static class Builder { - - private final List> services = new ArrayList<>(); - private final DeploymentManifestSchema.ProtocolMode protocolMode; - private Tracer tracer = OpenTelemetry.noop().getTracer("NOOP"); - - public Builder(DeploymentManifestSchema.ProtocolMode protocolMode) { - this.protocolMode = protocolMode; - } - - public Builder bind(ServiceDefinition component, O options) { - this.services.add(new ServiceAndOptions<>(component, options)); - return this; - } - - public Builder withTracer(Tracer tracer) { - this.tracer = tracer; - return this; - } - - public RestateEndpoint build() { - return new RestateEndpoint( - this.protocolMode, - this.services.stream() - .collect(Collectors.toMap(c -> c.service.getServiceName(), Function.identity())), - tracer); - } - } - - /** - * Interface to abstract setting the logging context variables. - * - *

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

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

Example usage: - * - *

- * public static void main(String[] args) {
- *   RestateHttpEndpointBuilder.builder()
- *           .bind(new Counter())
- *           .buildAndListen();
- * }
- * 
- */ -public class RestateHttpEndpointBuilder { - - private static final Logger LOG = LogManager.getLogger(RestateHttpEndpointBuilder.class); - - private final Vertx vertx; - private final RestateEndpoint.Builder endpointBuilder = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.BIDI_STREAM); - private OpenTelemetry openTelemetry = OpenTelemetry.noop(); - private HttpServerOptions options = - new HttpServerOptions() - .setPort(Optional.ofNullable(System.getenv("PORT")).map(Integer::parseInt).orElse(9080)); - - private RestateHttpEndpointBuilder(Vertx vertx) { - this.vertx = vertx; - } - - /** Create a new builder. */ - public static RestateHttpEndpointBuilder builder() { - return new RestateHttpEndpointBuilder(Vertx.vertx()); - } - - /** Create a new builder. */ - public static RestateHttpEndpointBuilder builder(Vertx vertx) { - return new RestateHttpEndpointBuilder(vertx); - } - - /** Add custom {@link HttpServerOptions} to the server used by the endpoint. */ - public RestateHttpEndpointBuilder withOptions(HttpServerOptions options) { - this.options = Objects.requireNonNull(options); - return this; - } - - /** - * Add a Restate service to the endpoint. This will automatically discover the generated factory - * based on the class name. - * - *

You can also manually instantiate the {@link BindableService} using {@link - * #bind(BindableService)}. - */ - public RestateHttpEndpointBuilder bind(Object service) { - return this.bind(RestateEndpoint.discoverBindableServiceFactory(service).create(service)); - } - - /** - * Add a Restate bindable service to the endpoint. - * - *

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

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

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

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

For example: - * - *

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

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

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

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

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

You can also manually instantiate the {@link BindableService} using {@link - * #with(BindableService)}. - */ - public RestateRunnerBuilder with(Object service) { - endpointBuilder.bind(service); - return this; - } - - /** - * Add a Restate bindable service to the endpoint. - * - *

To override the options, use {@link #with(BindableService, Object)}. - */ - public RestateRunnerBuilder with(BindableService service) { - endpointBuilder.bind(service); - return this; - } - - /** Add a Restate bindable service to the endpoint, overriding the options. */ - public RestateRunnerBuilder with(BindableService service, O options) { - endpointBuilder.bind(service, options); - return this; - } - - public ManualRestateRunner buildManualRunner() { - return new ManualRestateRunner( - this.endpointBuilder.build(), - this.restateContainerImage, - this.additionalEnv, - this.configFile); - } - - public RestateRunner buildRunner() { - return new RestateRunner(this.buildManualRunner()); - } - - public static RestateRunnerBuilder create() { - return new RestateRunnerBuilder(RestateHttpEndpointBuilder.builder()); - } - - /** Create from {@link RestateHttpEndpointBuilder}. */ - public static RestateRunnerBuilder of(RestateHttpEndpointBuilder endpointBuilder) { - return new RestateRunnerBuilder(endpointBuilder); - } -} diff --git a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateURL.java b/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateURL.java deleted file mode 100644 index c7d93d5b..00000000 --- a/sdk-testing/src/main/java/dev/restate/sdk/testing/RestateURL.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.net.URL; - -/** - * Inject Restate's URL (either {@link String} or {@link URL}) to interact with the deployed - * runtime. - */ -@Target(value = ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface RestateURL {} diff --git a/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java b/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java deleted file mode 100644 index cf30ca76..00000000 --- a/sdk-testing/src/test/java/dev/restate/sdk/testing/Counter.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.testing; - -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.annotation.Handler; -import dev.restate.sdk.annotation.VirtualObject; -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.StateKey; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@VirtualObject(name = "Counter") -public class Counter { - - private static final Logger LOG = LogManager.getLogger(Counter.class); - - private static final StateKey TOTAL = StateKey.of("total", CoreSerdes.JSON_LONG); - - @Handler - public void reset(ObjectContext ctx) { - ctx.clearAll(); - } - - @Handler - public void add(ObjectContext ctx, Long request) { - long currentValue = ctx.get(TOTAL).orElse(0L); - long newValue = currentValue + request; - ctx.set(TOTAL, newValue); - } - - @Handler - public long get(ObjectContext ctx) { - return ctx.get(TOTAL).orElse(0L); - } - - @Handler - public CounterUpdateResult getAndAdd(ObjectContext ctx, Long request) { - LOG.info("Invoked get and add with " + request); - - long currentValue = ctx.get(TOTAL).orElse(0L); - long newValue = currentValue + request; - ctx.set(TOTAL, newValue); - - return new CounterUpdateResult(newValue, currentValue); - } - - public static class CounterUpdateResult { - private final Long newValue; - private final Long oldValue; - - public CounterUpdateResult(Long newValue, Long oldValue) { - this.newValue = newValue; - this.oldValue = oldValue; - } - - public Long getNewValue() { - return newValue; - } - - public Long getOldValue() { - return oldValue; - } - } -} diff --git a/sdk-testing/src/test/java/dev/restate/sdk/testing/CounterTest.java b/sdk-testing/src/test/java/dev/restate/sdk/testing/CounterTest.java deleted file mode 100644 index c0a930db..00000000 --- a/sdk-testing/src/test/java/dev/restate/sdk/testing/CounterTest.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 dev.restate.sdk.testing; - -import static org.assertj.core.api.Assertions.assertThat; - -import dev.restate.sdk.client.IngressClient; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.api.extension.RegisterExtension; - -class CounterTest { - - @RegisterExtension - private static final RestateRunner RESTATE_RUNNER = - RestateRunnerBuilder.create() - .withRestateContainerImage( - "ghcr.io/restatedev/restate:main") // test against the latest main Restate image - .with(new Counter()) - .buildRunner(); - - @Test - @Timeout(value = 10) - void testGreet(@RestateIngressClient IngressClient ingressClient) { - var client = CounterClient.fromIngress(ingressClient, "my-counter"); - long response = client.get(); - - assertThat(response).isEqualTo(0L); - } -} diff --git a/sdk-testing/src/test/proto/counter.proto b/sdk-testing/src/test/proto/counter.proto deleted file mode 100644 index 065e1ac2..00000000 --- a/sdk-testing/src/test/proto/counter.proto +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -syntax = "proto3"; - -package counter; - -import "google/protobuf/empty.proto"; -import "dev/restate/ext.proto"; - -option java_multiple_files = true; -option java_package = "dev.restate.sdk.examples.generated"; -option java_outer_classname = "CounterProto"; - -service Counter { - option (dev.restate.ext.service_type) = KEYED; - - rpc Reset (CounterRequest) returns (google.protobuf.Empty); - rpc Add (CounterAddRequest) returns (google.protobuf.Empty); - rpc Get (CounterRequest) returns (GetResponse); - rpc GetAndAdd (CounterAddRequest) returns (CounterUpdateResult); -} - -message CounterRequest { - string counter_name = 1 [(dev.restate.ext.field) = KEY]; -} - -message CounterAddRequest { - string counter_name = 1 [(dev.restate.ext.field) = KEY]; - int64 value = 2; -} - -message GetResponse { - int64 value = 1; -} - -message CounterUpdateResult { - int64 old_value = 1; - int64 new_value = 2; -} \ No newline at end of file diff --git a/sdk-testing/src/test/resources/log4j2.properties b/sdk-testing/src/test/resources/log4j2.properties deleted file mode 100644 index 130894e5..00000000 --- a/sdk-testing/src/test/resources/log4j2.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Set to debug or trace if log4j initialization is failing -status = warn - -# Console appender configuration -appender.console.type = Console -appender.console.name = consoleLogger -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %notEmpty{[%X{restateServiceMethod}]}%notEmpty{[%X{restateInvocationId}]} %c - %m%n - -# Restate logs to debug level -logger.app.name = dev.restate -logger.app.level = debug -logger.app.additivity = false -logger.app.appenderRef.console.ref = consoleLogger - -# Root logger -rootLogger.level = info -rootLogger.appenderRef.stdout.ref = consoleLogger \ No newline at end of file diff --git a/sdk-workflow-api/build.gradle.kts b/sdk-workflow-api/build.gradle.kts deleted file mode 100644 index c2396816..00000000 --- a/sdk-workflow-api/build.gradle.kts +++ /dev/null @@ -1,54 +0,0 @@ -plugins { - `java-library` - `library-publishing-conventions` - alias(pluginLibs.plugins.protobuf) -} - -description = "Restate SDK Workflow APIs" - -dependencies { - compileOnly(coreLibs.jspecify) - - api(project(":sdk-common")) - api(project(":sdk-api")) - - implementation(coreLibs.protobuf.java) - implementation(coreLibs.log4j.core) - - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.annotations) - implementation(jacksonLibs.jackson.jsr310) - implementation(project(":sdk-serde-jackson")) - implementation(project(":sdk-serde-protobuf")) - - testImplementation(testingLibs.junit.jupiter) - testImplementation(testingLibs.assertj) - - // Import test suites from sdk-core - testImplementation(project(":sdk-core", "testArchive")) -} - -// Configure protobuf - -val protobufVersion = coreLibs.versions.protobuf.get() - -protobuf { protoc { artifact = "com.google.protobuf:protoc:$protobufVersion" } } - -// Make sure task dependencies are correct - -tasks { - withType { dependsOn(generateProto) } - withType { dependsOn(generateProto) } -} - -// Generate test jar - -configurations { register("testArchive") } - -tasks.register("testJar") { - archiveClassifier.set("tests") - - from(project.the()["test"].output) -} - -artifacts { add("testArchive", tasks["testJar"]) } diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromise.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromise.java deleted file mode 100644 index 5a2f4a0b..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromise.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.Awaitable; -import java.util.Optional; - -public interface DurablePromise { - Awaitable awaitable(); - - Optional peek(); - - boolean isCompleted(); -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseHandle.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseHandle.java deleted file mode 100644 index b9022180..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseHandle.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -public interface DurablePromiseHandle { - void resolve(T payload) throws IllegalStateException; - - void reject(String reason) throws IllegalStateException; -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseKey.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseKey.java deleted file mode 100644 index 758dead6..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/DurablePromiseKey.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.common.CoreSerdes; -import dev.restate.sdk.common.Serde; - -/** - * This class holds information about durable promise's name and its type tag to be used for - * serializing and deserializing it. - * - * @param the generic type of the signal. - */ -public final class DurablePromiseKey { - - private final String name; - private final Serde serde; - - private DurablePromiseKey(String name, Serde serde) { - this.name = name; - this.serde = serde; - } - - /** Create a new {@link DurablePromiseKey}. */ - public static DurablePromiseKey of(String name, Serde serde) { - return new DurablePromiseKey<>(name, serde); - } - - /** Create a new {@link DurablePromiseKey} for {@link String} state. */ - public static DurablePromiseKey string(String name) { - return new DurablePromiseKey<>(name, CoreSerdes.JSON_STRING); - } - - /** Create a new {@link DurablePromiseKey} for bytes state. */ - public static DurablePromiseKey raw(String name) { - return new DurablePromiseKey<>(name, CoreSerdes.RAW); - } - - public String name() { - return name; - } - - public Serde serde() { - return serde; - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowBuilder.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowBuilder.java deleted file mode 100644 index da2749bf..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowBuilder.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.Service; -import dev.restate.sdk.common.BindableService; -import dev.restate.sdk.common.HandlerType; -import dev.restate.sdk.workflow.impl.WorkflowImpl; -import java.util.HashMap; -import java.util.function.BiFunction; - -public final class WorkflowBuilder { - - private final String name; - private final Service.Handler workflowMethod; - private final HashMap> sharedMethods; - - private WorkflowBuilder(String name, Service.Handler workflowMethod) { - this.name = name; - this.workflowMethod = workflowMethod; - this.sharedMethods = new HashMap<>(); - } - - public WorkflowBuilder withShared( - Service.HandlerSignature sig, BiFunction runner) { - this.sharedMethods.put(sig.getName(), new Service.Handler<>(sig, HandlerType.SHARED, runner)); - return this; - } - - public BindableService build(Service.Options options) { - return new WorkflowImpl(name, options, workflowMethod, sharedMethods); - } - - public static WorkflowBuilder named( - String name, - Service.HandlerSignature sig, - BiFunction runner) { - return new WorkflowBuilder(name, new Service.Handler<>(sig, HandlerType.SHARED, runner)); - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowContext.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowContext.java deleted file mode 100644 index ff49578b..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowContext.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.common.StateKey; -import org.jspecify.annotations.NonNull; - -public interface WorkflowContext extends WorkflowSharedContext { - - void clear(StateKey key); - - void set(StateKey key, @NonNull T value); -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowExecutionState.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowExecutionState.java deleted file mode 100644 index 0dc06b41..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowExecutionState.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -public enum WorkflowExecutionState { - STARTED, - ALREADY_STARTED, - ALREADY_COMPLETED, -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowSharedContext.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowSharedContext.java deleted file mode 100644 index df17fe30..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/WorkflowSharedContext.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow; - -import dev.restate.sdk.Context; -import dev.restate.sdk.common.StateKey; -import java.util.Optional; - -public interface WorkflowSharedContext extends Context { - - String workflowKey(); - - Optional get(StateKey key); - - // -- Signals - - DurablePromise durablePromise(DurablePromiseKey key); - - DurablePromiseHandle durablePromiseHandle(DurablePromiseKey key); -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/InvokeRequest.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/InvokeRequest.java deleted file mode 100644 index 649ee5c7..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/InvokeRequest.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow.impl; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.Objects; - -public final class InvokeRequest { - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private final String key; - private final JsonNode payload; - - @JsonCreator - public InvokeRequest(@JsonProperty("key") String key, @JsonProperty("payload") JsonNode payload) { - this.key = key; - this.payload = payload; - } - - public String getKey() { - return key; - } - - public JsonNode getPayload() { - return payload; - } - - public static InvokeRequest fromAny(String key, Object value) { - return new InvokeRequest(key, OBJECT_MAPPER.convertValue(value, JsonNode.class)); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - InvokeRequest that = (InvokeRequest) o; - return Objects.equals(key, that.key) && Objects.equals(payload, that.payload); - } - - @Override - public int hashCode() { - return Objects.hash(key, payload); - } - - @Override - public String toString() { - return "InvokeRequest{" + "key='" + key + '\'' + ", payload=" + payload + '}'; - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java deleted file mode 100644 index 25070acc..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowCodegenUtil.java +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow.impl; - -import static dev.restate.sdk.workflow.impl.WorkflowImpl.workflowManagerObjectName; - -import dev.restate.sdk.Awaitable; -import dev.restate.sdk.Context; -import dev.restate.sdk.client.IngressClient; -import dev.restate.sdk.client.RequestOptions; -import dev.restate.sdk.common.*; -import dev.restate.sdk.workflow.WorkflowExecutionState; -import dev.restate.sdk.workflow.generated.GetOutputResponse; -import dev.restate.sdk.workflow.generated.GetStateResponse; -import java.time.Duration; -import java.util.Optional; -import org.jspecify.annotations.Nullable; - -// Methods invoked from code-generated classes -public final class WorkflowCodegenUtil { - - private WorkflowCodegenUtil() {} - - // -- Restate client methods - - public static class RestateClient { - private RestateClient() {} - - public static Awaitable submit( - Context ctx, String workflowName, String workflowKey, @Nullable Object payload) { - return ctx.call( - Target.service(workflowName, "submit"), - WorkflowImpl.INVOKE_REQUEST_SERDE, - WorkflowImpl.WORKFLOW_EXECUTION_STATE_SERDE, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static Awaitable> getOutput( - Context ctx, String workflowName, String workflowKey, Serde serde) { - return ctx.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getOutput"), - CoreSerdes.VOID, - WorkflowImpl.GET_OUTPUT_RESPONSE_SERDE, - null) - .map( - response -> { - if (response.hasNotCompleted()) { - return Optional.empty(); - } - if (response.hasFailure()) { - throw new TerminalException( - response.getFailure().getCode(), response.getFailure().getMessage()); - } - return Optional.ofNullable(serde.deserialize(response.getValue())); - }); - } - - public static Awaitable isCompleted( - Context ctx, String workflowName, String workflowKey) { - return ctx.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getOutput"), - CoreSerdes.VOID, - WorkflowImpl.GET_OUTPUT_RESPONSE_SERDE, - null) - .map( - response -> { - if (response.hasFailure()) { - throw new TerminalException( - response.getFailure().getCode(), response.getFailure().getMessage()); - } - return !response.hasNotCompleted(); - }); - } - - public static Awaitable invokeShared( - Context ctx, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload, - Serde resSerde) { - return ctx.call( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - resSerde, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static void invokeSharedSend( - Context ctx, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload) { - ctx.send( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - InvokeRequest.fromAny(workflowKey, payload)); - } - - public static void invokeSharedSendDelayed( - Context ctx, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload, - Duration delay) { - ctx.send( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - InvokeRequest.fromAny(workflowKey, payload), - delay); - } - - public static Awaitable> getState( - Context ctx, String workflowName, String workflowKey, StateKey key) { - return ctx.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getState"), - CoreSerdes.JSON_STRING, - WorkflowImpl.GET_STATE_RESPONSE_SERDE, - key.name()) - .map( - response -> { - if (response.hasEmpty()) { - return Optional.empty(); - } - return Optional.of(key.serde().deserialize(response.getValue())); - }); - } - } - - // --- External client methods - - public static class ExternalClient { - private ExternalClient() {} - - public static WorkflowExecutionState submit( - IngressClient ingressClient, - String workflowName, - String workflowKey, - @Nullable Object payload) { - return ingressClient.call( - Target.service(workflowName, "submit"), - WorkflowImpl.INVOKE_REQUEST_SERDE, - WorkflowImpl.WORKFLOW_EXECUTION_STATE_SERDE, - InvokeRequest.fromAny(workflowKey, payload), - RequestOptions.DEFAULT); - } - - public static Optional getOutput( - IngressClient ingressClient, String workflowName, String workflowKey, Serde serde) { - GetOutputResponse response = - ingressClient.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getOutput"), - CoreSerdes.VOID, - WorkflowImpl.GET_OUTPUT_RESPONSE_SERDE, - null, - RequestOptions.DEFAULT); - if (response.hasNotCompleted()) { - return Optional.empty(); - } - if (response.hasFailure()) { - throw new TerminalException( - response.getFailure().getCode(), response.getFailure().getMessage()); - } - return Optional.ofNullable(serde.deserialize(response.getValue())); - } - - public static boolean isCompleted( - IngressClient ingressClient, String workflowName, String workflowKey) { - GetOutputResponse response = - ingressClient.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getOutput"), - CoreSerdes.VOID, - WorkflowImpl.GET_OUTPUT_RESPONSE_SERDE, - null, - RequestOptions.DEFAULT); - if (response.hasFailure()) { - throw new TerminalException( - response.getFailure().getCode(), response.getFailure().getMessage()); - } - return !response.hasNotCompleted(); - } - - public static T invokeShared( - IngressClient ingressClient, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload, - Serde resSerde) { - return ingressClient.call( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - resSerde, - InvokeRequest.fromAny(workflowKey, payload), - RequestOptions.DEFAULT); - } - - public static void invokeSharedSend( - IngressClient ingressClient, - String workflowName, - String handlerName, - String workflowKey, - @Nullable Object payload) { - ingressClient.send( - Target.service(workflowName, handlerName), - WorkflowImpl.INVOKE_REQUEST_SERDE, - InvokeRequest.fromAny(workflowKey, payload), - null, - RequestOptions.DEFAULT); - } - - public static Optional getState( - IngressClient ingressClient, String workflowName, String workflowKey, StateKey key) { - GetStateResponse response = - ingressClient.call( - Target.virtualObject( - workflowManagerObjectName(workflowName), workflowKey, "getState"), - CoreSerdes.JSON_STRING, - WorkflowImpl.GET_STATE_RESPONSE_SERDE, - key.name(), - RequestOptions.DEFAULT); - if (response.hasEmpty()) { - return Optional.empty(); - } - return Optional.of(key.serde().deserialize(response.getValue())); - } - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java deleted file mode 100644 index 0a8e8702..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowContextImpl.java +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow.impl; - -import static dev.restate.sdk.workflow.impl.WorkflowImpl.workflowManagerObjectName; - -import dev.restate.sdk.*; -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.function.ThrowingSupplier; -import dev.restate.sdk.workflow.DurablePromise; -import dev.restate.sdk.workflow.DurablePromiseHandle; -import dev.restate.sdk.workflow.DurablePromiseKey; -import dev.restate.sdk.workflow.WorkflowContext; -import dev.restate.sdk.workflow.generated.*; -import java.time.Duration; -import java.util.Optional; -import org.jspecify.annotations.NonNull; - -class WorkflowContextImpl implements WorkflowContext { - - private final Context ctx; - private final String workflowFsqn; - private final String workflowKey; - private final boolean isExclusive; - - WorkflowContextImpl(Context ctx, String workflowFqsn, String workflowKey, boolean isExclusive) { - this.ctx = ctx; - this.workflowFsqn = workflowFqsn; - this.workflowKey = workflowKey; - this.isExclusive = isExclusive; - } - - @Override - public String workflowKey() { - return this.workflowKey; - } - - @Override - public Request request() { - return ctx.request(); - } - - // --- State ops - - @Override - public Optional get(StateKey key) { - GetStateResponse response = - this.ctx - .call( - workflowManagerTarget("getState"), - CoreSerdes.JSON_STRING, - WorkflowImpl.GET_STATE_RESPONSE_SERDE, - key.name()) - .await(); - - switch (response.getResultCase()) { - case VALUE: - return Optional.of(key.serde().deserialize(response.getValue())); - case EMPTY: - return Optional.empty(); - } - throw new IllegalStateException("Unexpected response from WorkflowManager"); - } - - @Override - public void clear(StateKey key) { - if (!isExclusive) { - throw new UnsupportedOperationException("Can't perform a state update on a SharedContext"); - } - this.ctx.send(workflowManagerTarget("clearState"), CoreSerdes.JSON_STRING, key.name()); - } - - @Override - public void set(StateKey key, @NonNull T value) { - if (!isExclusive) { - throw new UnsupportedOperationException("Can't perform a state update on a SharedContext"); - } - this.ctx.send( - workflowManagerTarget("setState"), - WorkflowImpl.SET_STATE_REQUEST_SERDE, - SetStateRequest.newBuilder() - .setStateKey(key.name()) - .setStateValue(key.serde().serializeToByteString(value)) - .build()); - } - - // -- Signal - - @Override - public DurablePromise durablePromise(DurablePromiseKey key) { - Awakeable awakeable = ctx.awakeable(key.serde()); - - // Register durablePromise - ctx.send( - workflowManagerTarget("waitDurablePromiseCompletion"), - WorkflowImpl.WAIT_DURABLE_PROMISE_COMPLETION_REQUEST_SERDE, - WaitDurablePromiseCompletionRequest.newBuilder() - .setDurablePromiseKey(key.name()) - .setAwakeableId(awakeable.id()) - .build()); - - return new DurablePromise<>() { - @Override - public Awaitable awaitable() { - return awakeable; - } - - @Override - public Optional peek() { - MaybeDurablePromiseCompletion maybeDurablePromiseCompletion = - ctx.call( - workflowManagerTarget("getDurablePromiseCompletion"), - CoreSerdes.JSON_STRING, - WorkflowImpl.MAYBE_DURABLE_PROMISE_COMPLETION_SERDE, - key.name()) - .await(); - - switch (maybeDurablePromiseCompletion.getResultCase()) { - case VALUE: - return Optional.of(key.serde().deserialize(maybeDurablePromiseCompletion.getValue())); - case FAILURE: - throw new TerminalException( - maybeDurablePromiseCompletion.getFailure().getCode(), - maybeDurablePromiseCompletion.getFailure().getMessage()); - case NOT_COMPLETED: - return Optional.empty(); - } - throw new IllegalStateException("Unexpected response from WorkflowManager"); - } - - @Override - public boolean isCompleted() { - MaybeDurablePromiseCompletion maybeDurablePromiseCompletion = - ctx.call( - workflowManagerTarget("getDurablePromiseCompletion"), - CoreSerdes.JSON_STRING, - WorkflowImpl.MAYBE_DURABLE_PROMISE_COMPLETION_SERDE, - key.name()) - .await(); - return !maybeDurablePromiseCompletion.hasNotCompleted(); - } - }; - } - - @Override - public DurablePromiseHandle durablePromiseHandle(DurablePromiseKey key) { - return new DurablePromiseHandle<>() { - @Override - public void resolve(T payload) throws IllegalStateException { - ctx.send( - workflowManagerTarget("completeDurablePromise"), - WorkflowImpl.COMPLETE_DURABLE_PROMISE_REQUEST_SERDE, - CompleteDurablePromiseRequest.newBuilder() - .setDurablePromiseKey(key.name()) - .setCompletion( - DurablePromiseCompletion.newBuilder() - .setValue(key.serde().serializeToByteString(payload))) - .build()); - } - - @Override - public void reject(String reason) throws IllegalStateException { - ctx.send( - workflowManagerTarget("completeDurablePromise"), - WorkflowImpl.COMPLETE_DURABLE_PROMISE_REQUEST_SERDE, - CompleteDurablePromiseRequest.newBuilder() - .setDurablePromiseKey(key.name()) - .setCompletion( - DurablePromiseCompletion.newBuilder() - .setFailure(Failure.newBuilder().setCode(2).setMessage(reason))) - .build()); - } - }; - } - - // -- Delegates to RestateContext - - @Override - public void sleep(Duration duration) { - ctx.sleep(duration); - } - - @Override - public Awaitable timer(Duration duration) { - return ctx.timer(duration); - } - - @Override - public Awaitable call( - Target target, Serde inputSerde, Serde outputSerde, T parameter) { - return ctx.call(target, inputSerde, outputSerde, parameter); - } - - @Override - public void send(Target target, Serde inputSerde, T parameter) { - ctx.send(target, inputSerde, parameter); - } - - @Override - public void send(Target target, Serde inputSerde, T parameter, Duration delay) { - ctx.send(target, inputSerde, parameter, delay); - } - - @Override - public T run(String name, Serde serde, ThrowingSupplier action) - throws TerminalException { - return ctx.run(name, serde, action); - } - - @Override - public Awakeable awakeable(Serde serde) { - return ctx.awakeable(serde); - } - - @Override - public AwakeableHandle awakeableHandle(String id) { - return ctx.awakeableHandle(id); - } - - @Override - public RestateRandom random() { - return ctx.random(); - } - - private Target workflowManagerTarget(String handler) { - return Target.virtualObject( - workflowManagerObjectName(this.workflowFsqn), this.workflowKey, handler); - } -} diff --git a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java b/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java deleted file mode 100644 index e5484dd4..00000000 --- a/sdk-workflow-api/src/main/java/dev/restate/sdk/workflow/impl/WorkflowImpl.java +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -package dev.restate.sdk.workflow.impl; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.google.protobuf.*; -import dev.restate.sdk.Context; -import dev.restate.sdk.ObjectContext; -import dev.restate.sdk.Service; -import dev.restate.sdk.Service.HandlerSignature; -import dev.restate.sdk.common.*; -import dev.restate.sdk.common.syscalls.ServiceDefinition; -import dev.restate.sdk.serde.jackson.JacksonSerdes; -import dev.restate.sdk.serde.protobuf.ProtobufSerdes; -import dev.restate.sdk.workflow.WorkflowContext; -import dev.restate.sdk.workflow.WorkflowExecutionState; -import dev.restate.sdk.workflow.generated.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.function.BiFunction; -import org.jspecify.annotations.Nullable; - -public class WorkflowImpl implements BindableService { - - public static final Serde INVOKE_REQUEST_SERDE = - JacksonSerdes.of(InvokeRequest.class); - static final Serde WORKFLOW_EXECUTION_STATE_SERDE = - JacksonSerdes.of(WorkflowExecutionState.class); - static final Serde GET_STATE_RESPONSE_SERDE = - ProtobufSerdes.of(GetStateResponse.parser()); - static final Serde SET_STATE_REQUEST_SERDE = - ProtobufSerdes.of(SetStateRequest.parser()); - static final Serde - WAIT_DURABLE_PROMISE_COMPLETION_REQUEST_SERDE = - ProtobufSerdes.of(WaitDurablePromiseCompletionRequest.parser()); - static final Serde MAYBE_DURABLE_PROMISE_COMPLETION_SERDE = - ProtobufSerdes.of(MaybeDurablePromiseCompletion.parser()); - static final Serde COMPLETE_DURABLE_PROMISE_REQUEST_SERDE = - ProtobufSerdes.of(CompleteDurablePromiseRequest.parser()); - static final Serde GET_OUTPUT_RESPONSE_SERDE = - ProtobufSerdes.of(GetOutputResponse.parser()); - private static final Serde SET_OUTPUT_REQUEST_SERDE = - ProtobufSerdes.of(SetOutputRequest.parser()); - private static final Serde DURABLE_PROMISE_COMPLETION_SERDE = - ProtobufSerdes.of(DurablePromiseCompletion.parser()); - - private static final Serde> DURABLEPROMISE_LISTENER_SERDE = - JacksonSerdes.of(new TypeReference<>() {}); - private static final StateKey OUTPUT_KEY = - StateKey.of("_output", ProtobufSerdes.of(MethodOutput.parser())); - - private static final StateKey WORKFLOW_EXECUTION_STATE_KEY = - StateKey.of("_workflow_execution_state", WORKFLOW_EXECUTION_STATE_SERDE); - private static final String START_HANDLER = "_start"; - - private final String name; - private final Service.Options options; - private final Service.Handler workflowMethod; - private final HashMap> sharedHandlers; - - public WorkflowImpl( - String name, - Service.Options options, - Service.Handler workflowMethod, - HashMap> sharedHandlers) { - this.name = name; - this.options = options; - this.workflowMethod = workflowMethod; - this.sharedHandlers = sharedHandlers; - } - - // --- Workflow methods - - private WorkflowExecutionState submit(Context objectContext, InvokeRequest invokeRequest) { - // Try start - var response = - objectContext - .call( - workflowManagerTarget(invokeRequest.getKey(), "tryStart"), - CoreSerdes.JSON_STRING, - WORKFLOW_EXECUTION_STATE_SERDE, - invokeRequest.getKey()) - .await(); - if (response.equals(WorkflowExecutionState.STARTED)) { - // Schedule start - objectContext.send( - Target.service(name, WorkflowImpl.START_HANDLER), INVOKE_REQUEST_SERDE, invokeRequest); - } - - return response; - } - - private void internalStart(Context context, InvokeRequest invokeRequest) { - // We can start now! - byte[] valueOutput; - try { - // Convert input - Object input = - this.workflowMethod - .getHandlerSignature() - .getRequestSerde() - .deserialize(invokeRequest.getPayload().toString().getBytes(StandardCharsets.UTF_8)); - - // Invoke run - WorkflowContext ctx = new WorkflowContextImpl(context, name, invokeRequest.getKey(), true); - @SuppressWarnings("unchecked") - Object output = - ((BiFunction) this.workflowMethod.getRunner()).apply(ctx, input); - - //noinspection unchecked - valueOutput = - ((Serde) this.workflowMethod.getHandlerSignature().getResponseSerde()) - .serialize(output); - } catch (TerminalException e) { - // Intercept TerminalException to record it - context.send( - workflowManagerTarget(invokeRequest.getKey(), "setOutput"), - SET_OUTPUT_REQUEST_SERDE, - SetOutputRequest.newBuilder() - .setOutput( - MethodOutput.newBuilder() - .setFailure( - Failure.newBuilder().setCode(e.getCode()).setMessage(e.getMessage()))) - .build()); - throw e; - } - - // Record output - context.send( - workflowManagerTarget(invokeRequest.getKey(), "setOutput"), - SET_OUTPUT_REQUEST_SERDE, - SetOutputRequest.newBuilder() - .setOutput( - MethodOutput.newBuilder().setValue(UnsafeByteOperations.unsafeWrap(valueOutput))) - .build()); - } - - private byte[] invokeSharedMethod(String handlerName, Context context, InvokeRequest request) { - // Lookup the method - @SuppressWarnings("unchecked") - Service.Handler method = - (Service.Handler) sharedHandlers.get(handlerName); - if (method == null) { - throw new TerminalException(404, "Method " + handlerName + " not found"); - } - - // Convert input - Object input = - method - .getHandlerSignature() - .getRequestSerde() - .deserialize(request.getPayload().toString().getBytes(StandardCharsets.UTF_8)); - - // Invoke method - WorkflowContext ctx = new WorkflowContextImpl(context, name, request.getKey(), false); - // We let the sdk core to manage the failures - Object output = method.getRunner().apply(ctx, input); - - return method.getHandlerSignature().getResponseSerde().serialize(output); - } - - // --- Workflow manager methods - - private GetStateResponse getState(ObjectContext context, String key) throws TerminalException { - return context - .get(stateKey(key)) - .map(val -> GetStateResponse.newBuilder().setValue(val).build()) - .orElseGet( - () -> GetStateResponse.newBuilder().setEmpty(Empty.getDefaultInstance()).build()); - } - - private void setState(ObjectContext context, SetStateRequest request) throws TerminalException { - context.set(stateKey(request.getStateKey()), request.getStateValue()); - } - - private void clearState(ObjectContext context, String key) throws TerminalException { - context.clear(stateKey(key)); - } - - private void waitDurablePromiseCompletion( - ObjectContext context, WaitDurablePromiseCompletionRequest request) throws TerminalException { - Optional val = - context.get(durablePromiseKey(request.getDurablePromiseKey())); - if (val.isPresent()) { - completeListener(context, request.getAwakeableId(), val.get()); - return; - } - - StateKey> listenersKey = durablePromiseListenersKey(request.getDurablePromiseKey()); - Set listeners = context.get(listenersKey).orElseGet(HashSet::new); - listeners.add(request.getAwakeableId()); - context.set(listenersKey, listeners); - } - - private MaybeDurablePromiseCompletion getDurablePromiseCompletion( - ObjectContext context, String durablePromiseKeyStr) throws TerminalException { - StateKey durablePromiseKey = durablePromiseKey(durablePromiseKeyStr); - Optional val = context.get(durablePromiseKey); - if (val.isEmpty()) { - return MaybeDurablePromiseCompletion.newBuilder() - .setNotCompleted(Empty.getDefaultInstance()) - .build(); - } - if (val.get().hasValue()) { - return MaybeDurablePromiseCompletion.newBuilder().setValue(val.get().getValue()).build(); - } - return MaybeDurablePromiseCompletion.newBuilder().setFailure(val.get().getFailure()).build(); - } - - private void completeDurablePromise(ObjectContext context, CompleteDurablePromiseRequest request) - throws TerminalException { - // User can decide whether they want to allow overwriting the previously resolved value or not - StateKey durablePromiseKey = - durablePromiseKey(request.getDurablePromiseKey()); - Optional val = context.get(durablePromiseKey); - if (val.isPresent()) { - throw new TerminalException("Can't complete an already completed durablePromise"); - } - context.set(durablePromiseKey, request.getCompletion()); - - StateKey> listenersKey = durablePromiseListenersKey(request.getDurablePromiseKey()); - Set listeners = context.get(listenersKey).orElse(Collections.emptySet()); - for (String listener : listeners) { - completeListener(context, listener, request.getCompletion()); - } - context.clear(listenersKey); - } - - private WorkflowExecutionState tryStart(ObjectContext context) throws TerminalException { - Optional maybeResponse = context.get(WORKFLOW_EXECUTION_STATE_KEY); - if (maybeResponse.isPresent()) { - return maybeResponse.get(); - } - - context.set(WORKFLOW_EXECUTION_STATE_KEY, WorkflowExecutionState.ALREADY_STARTED); - return WorkflowExecutionState.STARTED; - } - - private GetOutputResponse getOutput(ObjectContext context) throws TerminalException { - return context - .get(OUTPUT_KEY) - .map( - methodOutput -> - methodOutput.hasValue() - ? GetOutputResponse.newBuilder().setValue(methodOutput.getValue()).build() - : GetOutputResponse.newBuilder().setFailure(methodOutput.getFailure()).build()) - .orElseGet( - () -> - GetOutputResponse.newBuilder().setNotCompleted(Empty.getDefaultInstance()).build()); - } - - private void setOutput(ObjectContext context, SetOutputRequest request) throws TerminalException { - context.set(OUTPUT_KEY, request.getOutput()); - context.set(WORKFLOW_EXECUTION_STATE_KEY, WorkflowExecutionState.ALREADY_COMPLETED); - } - - private void cleanup(ObjectContext context) throws TerminalException { - context.clearAll(); - } - - // --- Util methods for WorkflowManager - - private StateKey stateKey(String key) { - return StateKey.of( - "_state_" + key, - new Serde<>() { - @Override - public byte[] serialize(@Nullable ByteString value) { - return value.toByteArray(); - } - - @Override - public ByteString serializeToByteString(@Nullable ByteString value) { - return value; - } - - @Override - public ByteString deserialize(ByteString byteString) { - return byteString; - } - - @Override - public ByteString deserialize(byte[] value) { - return UnsafeByteOperations.unsafeWrap(value); - } - }); - } - - private void completeListener( - ObjectContext context, String listener, DurablePromiseCompletion completion) { - if (completion.hasValue()) { - context - .awakeableHandle(listener) - .resolve(CoreSerdes.RAW, completion.getValue().toByteArray()); - } else { - context.awakeableHandle(listener).reject(completion.getFailure().getMessage()); - } - } - - private StateKey durablePromiseKey(String key) { - return StateKey.of("_durablePromise_" + key, DURABLE_PROMISE_COMPLETION_SERDE); - } - - private StateKey> durablePromiseListenersKey(String key) { - return StateKey.of("_durablePromise_listeners_" + key, DURABLEPROMISE_LISTENER_SERDE); - } - - static String workflowManagerObjectName(String workflowName) { - return workflowName + "_Manager"; - } - - private Target workflowManagerTarget(String key, String handler) { - return Target.virtualObject(workflowManagerObjectName(name), key, handler); - } - - // --- Services definition - - @Override - public Service.Options options() { - return options; - } - - @Override - public List> definitions() { - // Prepare workflow service - Service.ServiceBuilder workflowBuilder = - Service.service(name) - .with( - HandlerSignature.of("submit", INVOKE_REQUEST_SERDE, WORKFLOW_EXECUTION_STATE_SERDE), - this::submit) - .with( - HandlerSignature.of(START_HANDLER, INVOKE_REQUEST_SERDE, CoreSerdes.VOID), - (context, invokeRequest) -> { - this.internalStart(context, invokeRequest); - return null; - }); - - // Append shared methods - for (var sharedMethod : sharedHandlers.values()) { - workflowBuilder.with( - HandlerSignature.of( - sharedMethod.getHandlerSignature().getName(), INVOKE_REQUEST_SERDE, CoreSerdes.RAW), - (context, invokeRequest) -> - this.invokeSharedMethod( - sharedMethod.getHandlerSignature().getName(), context, invokeRequest)); - } - - // Prepare workflow manager service - Service workflowManager = - Service.virtualObject(workflowManagerObjectName(name)) - .withExclusive( - HandlerSignature.of("getState", CoreSerdes.JSON_STRING, GET_STATE_RESPONSE_SERDE), - this::getState) - .withExclusive( - HandlerSignature.of("setState", SET_STATE_REQUEST_SERDE, CoreSerdes.VOID), - (context, setStateRequest) -> { - this.setState(context, setStateRequest); - return null; - }) - .withExclusive( - HandlerSignature.of("clearState", CoreSerdes.JSON_STRING, CoreSerdes.VOID), - (context, s) -> { - this.clearState(context, s); - return null; - }) - .withExclusive( - HandlerSignature.of( - "waitDurablePromiseCompletion", - WAIT_DURABLE_PROMISE_COMPLETION_REQUEST_SERDE, - CoreSerdes.VOID), - (context, waitDurablePromiseCompletionRequest) -> { - this.waitDurablePromiseCompletion(context, waitDurablePromiseCompletionRequest); - return null; - }) - .withExclusive( - HandlerSignature.of( - "getDurablePromiseCompletion", - CoreSerdes.JSON_STRING, - MAYBE_DURABLE_PROMISE_COMPLETION_SERDE), - this::getDurablePromiseCompletion) - .withExclusive( - HandlerSignature.of( - "completeDurablePromise", - COMPLETE_DURABLE_PROMISE_REQUEST_SERDE, - CoreSerdes.VOID), - (context, completeDurablePromiseRequest) -> { - this.completeDurablePromise(context, completeDurablePromiseRequest); - return null; - }) - .withExclusive( - HandlerSignature.of("tryStart", CoreSerdes.VOID, WORKFLOW_EXECUTION_STATE_SERDE), - (context, unused) -> this.tryStart(context)) - .withExclusive( - HandlerSignature.of("getOutput", CoreSerdes.VOID, GET_OUTPUT_RESPONSE_SERDE), - (context, unused) -> this.getOutput(context)) - .withExclusive( - HandlerSignature.of("setOutput", SET_OUTPUT_REQUEST_SERDE, CoreSerdes.VOID), - (context, setOutputRequest) -> { - this.setOutput(context, setOutputRequest); - return null; - }) - .withExclusive( - HandlerSignature.of("cleanup", CoreSerdes.VOID, CoreSerdes.VOID), - (context, unused) -> { - this.cleanup(context); - return null; - }) - .build(options); - - return List.of( - workflowBuilder.build(options).definitions().get(0), workflowManager.definitions().get(0)); - } -} diff --git a/sdk-workflow-api/src/main/proto/dev/restate/sdk/workflow/workflow.proto b/sdk-workflow-api/src/main/proto/dev/restate/sdk/workflow/workflow.proto deleted file mode 100644 index 4fa7390b..00000000 --- a/sdk-workflow-api/src/main/proto/dev/restate/sdk/workflow/workflow.proto +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH -// -// This file is part of the Restate Java SDK, -// which is released under the MIT license. -// -// You can find a copy of the license in file LICENSE in the root -// directory of this repository or package, or at -// https://github.com/restatedev/sdk-java/blob/main/LICENSE -syntax = "proto3"; - -package dev.restate.sdk.workflow; - -import "google/protobuf/empty.proto"; -import "google/protobuf/struct.proto"; - -option java_multiple_files = true; -option java_package = "dev.restate.sdk.workflow.generated"; -option java_outer_classname = "WorkflowProto"; - -message GetStateResponse { - oneof result { - bytes value = 1; - google.protobuf.Empty empty = 2; - } -} - -message SetStateRequest { - string state_key = 2; - bytes state_value = 3; -} - -message GetOutputResponse { - oneof result { - bytes value = 1; - Failure failure = 2; - google.protobuf.Empty not_completed = 3; - }; -} - -message SetOutputRequest { - MethodOutput output = 2; -} - -message WaitDurablePromiseCompletionRequest { - string durable_promise_key = 2; - string awakeable_id = 3; -} - -message DurablePromiseCompletion { - oneof result { - bytes value = 1; - Failure failure = 2; - }; -} - -message MaybeDurablePromiseCompletion { - oneof result { - bytes value = 1; - Failure failure = 2; - google.protobuf.Empty not_completed = 3; - }; -} - -message CompleteDurablePromiseRequest { - string durable_promise_key = 2; - DurablePromiseCompletion completion = 3; -} - -message MethodOutput { - oneof result { - bytes value = 1; - Failure failure = 2; - } -} - -message Failure { - uint32 code = 1; - string message = 2; -} \ No newline at end of file diff --git a/sdk-core/src/main/service-protocol/service-invocation-protocol.md b/service-invocation-protocol.md similarity index 88% rename from sdk-core/src/main/service-protocol/service-invocation-protocol.md rename to service-invocation-protocol.md index f7a1a0b4..22635bc1 100644 --- a/sdk-core/src/main/service-protocol/service-invocation-protocol.md +++ b/service-invocation-protocol.md @@ -133,7 +133,15 @@ The `EndMessage` marks the end of the invocation lifecycle, that is the end of t ### Initiating the stream -When opening the stream, the HTTP request method MUST be `POST` and the request path MUST have the following format: +As described above, the runtime opens an HTTP request to the SDK to initiate the message stream. + +#### Method + +The request method used is always `POST`. + +#### Path + +The request path has the following format: ``` /invoke/{serviceName}/{handlerName} @@ -150,9 +158,28 @@ An arbitrary path MAY prepend the aforementioned path format. In case the path format is not respected, or `serviceName` or `handlerName` is unknown, the SDK MUST close the stream replying back with a `404` status code. -In case the invocation is accepted, `200` status code MUST be returned. +#### Content type and protocol version + +The request contains the content-type `application/vnd.restate.invocation.vX` where `X` is the service protocol version +chosen by the runtime, e.g.: + +```http request +content-type: application/vnd.restate.invocation.v1 +``` + +The service protocol version is defined by `ServiceProtocolVersion` in +[`protocol.proto`](dev/restate/service/protocol.proto). + +The SDK MUST return back the same content-type in the successful response case. If the SDK doesn't support the +content-type, It SHOULD close the stream replying back with a `415` status code. + +#### Stream ready -Additionally, the header `x-restate-server` MAY be sent back, with the following format: +To notify that the stream is ready to be used, the SDK MUST reply with `200` status code. + +#### SDK version + +The SDK MAY send back the response header `x-restate-server`: ```http request x-restate-server: / @@ -198,15 +225,14 @@ The `StartMessage` carries the metadata required to bootstrap the invocation sta 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | 0x0000 | Reserved | PV | + | 0x0000 | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Flags: -- 6 bits (MSB): Reserved -- 10 bits `PV`: Protocol version. Mask: `0x0000_03FF_0000_0000` +- 16 bits: Reserved ### Entries and Completions @@ -319,6 +345,9 @@ descriptions in [`protocol.proto`](dev/restate/service/protocol.proto). | `ClearStateEntryMessage` | `0x0801` | No | No | Clear the value of a service instance state key. | | `ClearAllStateEntryMessage` | `0x0802` | No | No | Clear all the values of the service instance state. | | `RunEntryMessage` | `0x0C05` | No | No | Run non-deterministic user provided code and persist the result. | +| `GetPromiseEntryMessage` | `0x0808` | Yes | No | Get or wait the value of the given promise. If the value is not present yet, this entry will block waiting for the value. | +| `PeekPromiseEntryMessage` | `0x0809` | Yes | No | Get the value of the given promise. If the value is not present, this entry completes immediately with empty completion. | +| `CompletePromiseEntryMessage` | `0x080A` | Yes | No | Complete the given promise. If the promise was completed already, this entry completes with a failure. | #### Awakeable identifier @@ -380,6 +409,29 @@ closing the stream afterward. | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +## Endpoint discovery + +Restate expects SDKs to provide reflective information about the exposed services and the supported protocol versions at +`/discovery`. These reflective information are propagated through an _endpoint manifest_. This document MUST follow the +schema defined in [endpoint_manifest_schema.json](./endpoint_manifest_schema.json) and is identified by the content-type +string `application/vnd.restate.endpointmanifest.vX+json`, where `X` is the manifest version. + +When sending the discovery request, the Restate runtime might specify a set of supported endpoint manifest schemas in +the [`Accept`](https://httpwg.org/specs/rfc9110.html#field.accept) header, for example: + +```http +accept: application/vnd.restate.endpointmanifest.v2+json, application/vnd.restate.endpointmanifest.v1+json +``` + +When replying, the content-type MUST contain the chosen endpoint manifest type/version: + +```http +content-type: application/vnd.restate.endpointmanifest.v1+json +``` + +The service discovery protocol version is defined by `ServiceDiscoveryProtocolVersion` in +[`discovery.proto`](dev/restate/service/discovery.proto). + ## Optional features The following section describes optional features SDK developers MAY implement to improve the experience and provide diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index 95eaba56..00000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/7.4.2/userguide/multi_project_builds.html - */ - -rootProject.name = "sdk-java" - -plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0" } - -include( - "admin-client", - "sdk-common", - "sdk-api", - "sdk-api-kotlin", - "sdk-core", - "sdk-serde-jackson", - "sdk-serde-protobuf", - "sdk-http-vertx", - "sdk-lambda", - "sdk-testing", - "sdk-api-gen-common", - "sdk-api-gen", - "sdk-api-kotlin-gen", - "sdk-workflow-api", - "examples", -) - -dependencyResolutionManagement { - repositories { mavenCentral() } - - versionCatalogs { - create("coreLibs") { - version("protobuf", "3.24.3") - version("log4j", "2.22.0") - version("opentelemetry", "1.30.1") - - library("protoc", "com.google.protobuf", "protoc").versionRef("protobuf") - library("protobuf-java", "com.google.protobuf", "protobuf-java").versionRef("protobuf") - library("protobuf-kotlin", "com.google.protobuf", "protobuf-kotlin").versionRef("protobuf") - - library("log4j-api", "org.apache.logging.log4j", "log4j-api").versionRef("log4j") - library("log4j-core", "org.apache.logging.log4j", "log4j-core").versionRef("log4j") - - library("opentelemetry-bom", "io.opentelemetry", "opentelemetry-bom") - .versionRef("opentelemetry") - library("opentelemetry-api", "io.opentelemetry", "opentelemetry-api").withoutVersion() - library("opentelemetry-semconv", "io.opentelemetry:opentelemetry-semconv:1.19.0-alpha") - - library("jspecify", "org.jspecify", "jspecify").version("0.3.0") - } - create("vertxLibs") { - library("vertx-bom", "io.vertx:vertx-stack-depchain:4.5.1") - library("vertx-core", "io.vertx", "vertx-core").withoutVersion() - library("vertx-kotlin-coroutines", "io.vertx", "vertx-lang-kotlin-coroutines") - .withoutVersion() - library("vertx-junit5", "io.vertx", "vertx-junit5").withoutVersion() - } - create("lambdaLibs") { - library("core", "com.amazonaws:aws-lambda-java-core:1.2.2") - library("events", "com.amazonaws:aws-lambda-java-events:3.11.0") - } - create("jacksonLibs") { - version("jackson", "2.16.1") - - library("jackson-bom", "com.fasterxml.jackson", "jackson-bom").versionRef("jackson") - library("jackson-annotations", "com.fasterxml.jackson.core", "jackson-annotations") - .withoutVersion() - library("jackson-core", "com.fasterxml.jackson.core", "jackson-core").withoutVersion() - library("jackson-databind", "com.fasterxml.jackson.core", "jackson-databind").withoutVersion() - library("jackson-jsr310", "com.fasterxml.jackson.datatype", "jackson-datatype-jsr310") - .withoutVersion() - library("jackson-jdk8", "com.fasterxml.jackson.datatype", "jackson-datatype-jdk8") - .withoutVersion() - } - create("kotlinLibs") { - library("kotlinx-coroutines", "org.jetbrains.kotlinx", "kotlinx-coroutines-core") - .version("1.7.3") - library("kotlinx-serialization-core", "org.jetbrains.kotlinx", "kotlinx-serialization-core") - .version("1.6.2") - library("kotlinx-serialization-json", "org.jetbrains.kotlinx", "kotlinx-serialization-json") - .version("1.6.2") - - version("ksp", "1.9.22-1.0.18") - library("symbol-processing-api", "com.google.devtools.ksp", "symbol-processing-api") - .versionRef("ksp") - plugin("ksp", "com.google.devtools.ksp").versionRef("ksp") - } - create("testingLibs") { - version("junit-jupiter", "5.9.1") - version("assertj", "3.23.1") - version("testcontainers", "1.19.4") - - library("junit-jupiter", "org.junit.jupiter", "junit-jupiter").versionRef("junit-jupiter") - library("junit-api", "org.junit.jupiter", "junit-jupiter-api").versionRef("junit-jupiter") - - library("assertj", "org.assertj", "assertj-core").versionRef("assertj") - - library("testcontainers-core", "org.testcontainers", "testcontainers") - .versionRef("testcontainers") - library("testcontainers-toxiproxy", "org.testcontainers", "toxiproxy") - .versionRef("testcontainers") - } - create("pluginLibs") { - plugin("spotless", "com.diffplug.spotless").version("6.22.0") - plugin("protobuf", "com.google.protobuf").version("0.9.4") - plugin("test-logger", "com.adarshr.test-logger").version("4.0.0") - } - } -} From 182c52188774641a9ed12efca5525d010881ea8e Mon Sep 17 00:00:00 2001 From: Till Rohrmann Date: Thu, 16 May 2024 21:10:58 +0200 Subject: [PATCH 2/6] Make Java SDK work with updated service-protocol --- sdk-core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-core/build.gradle.kts b/sdk-core/build.gradle.kts index 666ddf63..20ec2d74 100644 --- a/sdk-core/build.gradle.kts +++ b/sdk-core/build.gradle.kts @@ -43,7 +43,7 @@ sourceSets { // Configure jsonSchema2Pojo jsonSchema2Pojo { - setSource(files("$projectDir/src/main/service-protocol/deployment_manifest_schema.json")) + setSource(files("$projectDir/src/main/service-protocol/endpoint_manifest_schema.json")) targetPackage = "dev.restate.sdk.core.manifest" targetDirectory = generatedJ2SPDir.get().asFile From 6ca65043a132e3cd7c8449dd2102b1f14c622c94 Mon Sep 17 00:00:00 2001 From: Till Rohrmann Date: Thu, 16 May 2024 21:42:33 +0200 Subject: [PATCH 3/6] Add support for service discovery protocol version selection --- .../http/vertx/RequestHttpServerHandler.java | 81 +++++++++++++++---- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java index c7f0e2c1..e5d070cc 100644 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java +++ b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java @@ -8,12 +8,14 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.http.vertx; +import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; import static io.netty.handler.codec.http.HttpResponseStatus.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import dev.restate.generated.service.discovery.Discovery; import dev.restate.sdk.core.ProtocolException; import dev.restate.sdk.core.ResolvedEndpointHandler; import dev.restate.sdk.core.RestateEndpoint; @@ -31,6 +33,8 @@ import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.impl.HttpServerRequestInternal; import java.net.URI; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.Executor; import java.util.regex.Pattern; import org.apache.logging.log4j.LogManager; @@ -152,22 +156,69 @@ private Executor currentContextExecutor(Context currentContext) { } private void handleDiscoveryRequest(HttpServerRequest request) { - // Compute response and write it back - DeploymentManifestSchema response = this.restateEndpoint.handleDiscoveryRequest(); - Buffer responseBuffer; - try { - responseBuffer = Buffer.buffer(MANIFEST_OBJECT_MAPPER.writeValueAsBytes(response)); - } catch (JsonProcessingException e) { - LOG.warn("Error when writing out the manifest POJO", e); - request.response().setStatusCode(INTERNAL_SERVER_ERROR.code()).end(); - return; + final String accept = request.getHeader(ACCEPT); + + final Discovery.ServiceDiscoveryProtocolVersion serviceDiscoveryProtocolVersion = selectSupportedServiceDiscoveryProtocolVersion(accept); + + if (serviceDiscoveryProtocolVersion == Discovery.ServiceDiscoveryProtocolVersion.V1) { + // Compute response and write it back + DeploymentManifestSchema response = this.restateEndpoint.handleDiscoveryRequest(); + + Buffer responseBuffer; + try { + responseBuffer = Buffer.buffer(MANIFEST_OBJECT_MAPPER.writeValueAsBytes(response)); + } catch (JsonProcessingException e) { + LOG.warn("Error when writing out the manifest POJO", e); + request.response().setStatusCode(INTERNAL_SERVER_ERROR.code()).end(); + return; + } + + request + .response() + .setStatusCode(OK.code()) + .putHeader(X_RESTATE_SERVER_KEY, X_RESTATE_SERVER_VALUE) + .putHeader(CONTENT_TYPE, serviceDiscoveryProtocolVersionToContentType(serviceDiscoveryProtocolVersion)) + .end(responseBuffer); + } else { + request.response().setStatusCode(UNSUPPORTED_MEDIA_TYPE.code()).setStatusMessage("Service endpoint does not support the accepted service discovery protocol versions.").end(); + } + } + + private static Discovery.ServiceDiscoveryProtocolVersion selectSupportedServiceDiscoveryProtocolVersion(String accept) { + // assume V1 in case nothing was set + if (accept == null || accept.isEmpty()) { + return Discovery.ServiceDiscoveryProtocolVersion.V1; } - request - .response() - .setStatusCode(OK.code()) - .putHeader(X_RESTATE_SERVER_KEY, X_RESTATE_SERVER_VALUE) - .putHeader(CONTENT_TYPE, APPLICATION_JSON) - .end(responseBuffer); + final String[] supportedVersions = accept.split(","); + + Discovery.ServiceDiscoveryProtocolVersion maxVersion = Discovery.ServiceDiscoveryProtocolVersion.UNRECOGNIZED; + + for (String versionString: supportedVersions) { + final Optional optionalVersion = parseServiceDiscoveryProtocolVersion(versionString.trim()); + + if (optionalVersion.isPresent()) { + final Discovery.ServiceDiscoveryProtocolVersion version = optionalVersion.get(); + if (version.getNumber() > maxVersion.getNumber()) { + maxVersion = version; + } + } + } + + return maxVersion; + } + + private static Optional parseServiceDiscoveryProtocolVersion(String versionString) { + if (versionString.equals("application/vnd.restate.endpointmanifest.v1+json")) { + return Optional.of(Discovery.ServiceDiscoveryProtocolVersion.V1); + } + return Optional.empty(); + } + + private static String serviceDiscoveryProtocolVersionToContentType(Discovery.ServiceDiscoveryProtocolVersion version) { + if (Objects.requireNonNull(version) == Discovery.ServiceDiscoveryProtocolVersion.V1) { + return "application/vnd.restate.endpointmanifest.v1+json"; + } + return ""; } } From ff64549ee38f215c507c34c838a8324853c32bc2 Mon Sep 17 00:00:00 2001 From: Till Rohrmann Date: Thu, 16 May 2024 22:20:06 +0200 Subject: [PATCH 4/6] Extract service (discovery) protocol version from the http request headers This commit lets the Java SDK extract the accepted ServiceDiscoveryProtocolVersions from the accept header of the discovery request and sends a correspondingly encoded response back if the endpoint supports the service discovery protocol version. Likewise the SDK is now extracting the service protocol version from the content type header when the service is invoked. If the endpoint does not support the specified protocol version, then it will reject the request with a status code 415. --- sdk-core/build.gradle.kts | 1 + .../restate/sdk/core/DeploymentManifest.java | 8 +- .../dev/restate/sdk/core/ServiceProtocol.java | 136 ++++++++++++++++++ .../restate/sdk/core/MessageHeaderTest.java | 1 - sdk-http-vertx/build.gradle.kts | 4 - .../http/vertx/RequestHttpServerHandler.java | 113 ++++++++------- .../sdk/http/vertx/HttpVertxTestExecutor.kt | 13 +- .../sdk/http/vertx/RestateHttpEndpointTest.kt | 26 +++- sdk-lambda/build.gradle.kts | 4 - .../sdk/lambda/RestateLambdaEndpoint.java | 102 +++++++++---- .../restate/sdk/lambda/LambdaHandlerTest.java | 25 +++- 11 files changed, 331 insertions(+), 102 deletions(-) create mode 100644 sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java diff --git a/sdk-core/build.gradle.kts b/sdk-core/build.gradle.kts index 20ec2d74..43ae4030 100644 --- a/sdk-core/build.gradle.kts +++ b/sdk-core/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { // We need this for the manifest implementation(platform(jacksonLibs.jackson.bom)) implementation(jacksonLibs.jackson.annotations) + implementation(jacksonLibs.jackson.databind) // We don't want a hard-dependency on it compileOnly(coreLibs.log4j.core) diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java b/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java index 11d05dbe..ad01e64e 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java @@ -8,6 +8,9 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.core; +import static dev.restate.sdk.core.ServiceProtocol.MAX_SERVICE_PROTOCOL_VERSION; +import static dev.restate.sdk.core.ServiceProtocol.MIN_SERVICE_PROTOCOL_VERSION; + import dev.restate.sdk.common.HandlerType; import dev.restate.sdk.common.ServiceType; import dev.restate.sdk.common.syscalls.HandlerDefinition; @@ -18,6 +21,7 @@ import java.util.stream.Stream; final class DeploymentManifest { + private static final Input EMPTY_INPUT = new Input(); private static final Output EMPTY_OUTPUT = new Output().withSetContentTypeIfEmpty(false); @@ -27,8 +31,8 @@ public DeploymentManifest( DeploymentManifestSchema.ProtocolMode protocolMode, Stream> components) { this.manifest = new DeploymentManifestSchema() - .withMinProtocolVersion(1) - .withMaxProtocolVersion(1) + .withMinProtocolVersion(MIN_SERVICE_PROTOCOL_VERSION.getNumber()) + .withMaxProtocolVersion(MAX_SERVICE_PROTOCOL_VERSION.getNumber()) .withProtocolMode(protocolMode) .withServices( components diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java b/sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java new file mode 100644 index 00000000..ce0eaf79 --- /dev/null +++ b/sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java @@ -0,0 +1,136 @@ +// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH +// +// This file is part of the Restate Java SDK, +// which is released under the MIT license. +// +// You can find a copy of the license in file LICENSE in the root +// directory of this repository or package, or at +// https://github.com/restatedev/sdk-java/blob/main/LICENSE +package dev.restate.sdk.core; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.restate.generated.service.discovery.Discovery; +import dev.restate.generated.service.protocol.Protocol; +import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import java.util.Objects; +import java.util.Optional; + +public class ServiceProtocol { + public static final Protocol.ServiceProtocolVersion MIN_SERVICE_PROTOCOL_VERSION = + Protocol.ServiceProtocolVersion.V1; + public static final Protocol.ServiceProtocolVersion MAX_SERVICE_PROTOCOL_VERSION = + Protocol.ServiceProtocolVersion.V1; + + public static final Discovery.ServiceDiscoveryProtocolVersion + MIN_SERVICE_DISCOVERY_PROTOCOL_VERSION = Discovery.ServiceDiscoveryProtocolVersion.V1; + public static final Discovery.ServiceDiscoveryProtocolVersion + MAX_SERVICE_DISCOVERY_PROTOCOL_VERSION = Discovery.ServiceDiscoveryProtocolVersion.V1; + + public static Protocol.ServiceProtocolVersion parseServiceProtocolVersion(String version) { + version = version.trim(); + + if (version.equals("application/vnd.restate.invocation.v1")) { + return Protocol.ServiceProtocolVersion.V1; + } + return Protocol.ServiceProtocolVersion.SERVICE_PROTOCOL_VERSION_UNSPECIFIED; + } + + public static String serviceProtocolVersionToHeaderValue( + Protocol.ServiceProtocolVersion version) { + if (Objects.requireNonNull(version) == Protocol.ServiceProtocolVersion.V1) { + return "application/vnd.restate.invocation.v1"; + } + throw new IllegalArgumentException(String.format("Service protocol version '%s' has no header value", version.getNumber())); + } + + public static boolean is_supported(Protocol.ServiceProtocolVersion serviceProtocolVersion) { + return MIN_SERVICE_PROTOCOL_VERSION.getNumber() <= serviceProtocolVersion.getNumber() + && serviceProtocolVersion.getNumber() <= MAX_SERVICE_PROTOCOL_VERSION.getNumber(); + } + + public static boolean is_supported( + Discovery.ServiceDiscoveryProtocolVersion serviceDiscoveryProtocolVersion) { + return MIN_SERVICE_DISCOVERY_PROTOCOL_VERSION.getNumber() + <= serviceDiscoveryProtocolVersion.getNumber() + && serviceDiscoveryProtocolVersion.getNumber() + <= MAX_SERVICE_DISCOVERY_PROTOCOL_VERSION.getNumber(); + } + + /** + * Selects the highest supported service protocol version from a list of supported versions. + * + * @param acceptedVersionsString A comma-separated list of accepted service protocol versions. + * @return The highest supported service protocol version, otherwise {@link + * Protocol.ServiceProtocolVersion.SERVICE_PROTOCOL_VERSION_UNSPECIFIED} + */ + public static Discovery.ServiceDiscoveryProtocolVersion + selectSupportedServiceDiscoveryProtocolVersion(String acceptedVersionsString) { + // assume V1 in case nothing was set + if (acceptedVersionsString == null || acceptedVersionsString.isEmpty()) { + return Discovery.ServiceDiscoveryProtocolVersion.V1; + } + + final String[] supportedVersions = acceptedVersionsString.split(","); + + Discovery.ServiceDiscoveryProtocolVersion maxVersion = + Discovery.ServiceDiscoveryProtocolVersion.SERVICE_DISCOVERY_PROTOCOL_VERSION_UNSPECIFIED; + + for (String versionString : supportedVersions) { + final Optional optionalVersion = + parseServiceDiscoveryProtocolVersion(versionString); + + if (optionalVersion.isPresent()) { + final Discovery.ServiceDiscoveryProtocolVersion version = optionalVersion.get(); + if (is_supported(version) && version.getNumber() > maxVersion.getNumber()) { + maxVersion = version; + } + } + } + + return maxVersion; + } + + public static Optional + parseServiceDiscoveryProtocolVersion(String versionString) { + versionString = versionString.trim(); + + if (versionString.equals("application/vnd.restate.endpointmanifest.v1+json")) { + return Optional.of(Discovery.ServiceDiscoveryProtocolVersion.V1); + } + return Optional.empty(); + } + + public static String serviceDiscoveryProtocolVersionToHeaderValue( + Discovery.ServiceDiscoveryProtocolVersion version) { + if (Objects.requireNonNull(version) == Discovery.ServiceDiscoveryProtocolVersion.V1) { + return "application/vnd.restate.endpointmanifest.v1+json"; + } + throw new IllegalArgumentException(String.format("Service discovery protocol version '%s' has no header value", version.getNumber())); + } + + public static class DiscoveryResponseSerializer { + private static final ObjectMapper MANIFEST_OBJECT_MAPPER = new ObjectMapper(); + + private final Discovery.ServiceDiscoveryProtocolVersion serviceDiscoveryProtocolVersion; + + public DiscoveryResponseSerializer( + Discovery.ServiceDiscoveryProtocolVersion serviceDiscoveryProtocolVersion) { + if (!is_supported(serviceDiscoveryProtocolVersion)) { + throw new IllegalArgumentException("Unsupported service discovery protocol version"); + } + + this.serviceDiscoveryProtocolVersion = serviceDiscoveryProtocolVersion; + } + + public byte[] serialize(DeploymentManifestSchema response) throws Exception { + if (this.serviceDiscoveryProtocolVersion == Discovery.ServiceDiscoveryProtocolVersion.V1) { + return MANIFEST_OBJECT_MAPPER.writeValueAsBytes(response); + } + + throw new IllegalStateException( + String.format( + "DiscoveryResponseSerializer does not support service discovery protocol '%s'", + this.serviceDiscoveryProtocolVersion.getNumber())); + } + } +} diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java b/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java index 7f48cb39..09b0dc02 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java @@ -9,7 +9,6 @@ package dev.restate.sdk.core; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; diff --git a/sdk-http-vertx/build.gradle.kts b/sdk-http-vertx/build.gradle.kts index 67b619a6..2af8bb5c 100644 --- a/sdk-http-vertx/build.gradle.kts +++ b/sdk-http-vertx/build.gradle.kts @@ -16,10 +16,6 @@ dependencies { implementation(platform(vertxLibs.vertx.bom)) implementation(vertxLibs.vertx.core) - // Jackson (we need it for the manifest) - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.databind) - // Observability implementation(platform(coreLibs.opentelemetry.bom)) implementation(coreLibs.opentelemetry.api) diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java index e5d070cc..73f4c39c 100644 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java +++ b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java @@ -8,17 +8,18 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.http.vertx; +import static dev.restate.sdk.core.ServiceProtocol.selectSupportedServiceDiscoveryProtocolVersion; +import static dev.restate.sdk.core.ServiceProtocol.serviceDiscoveryProtocolVersionToHeaderValue; import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; import static io.netty.handler.codec.http.HttpResponseStatus.*; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import dev.restate.generated.service.discovery.Discovery; +import dev.restate.generated.service.protocol.Protocol; import dev.restate.sdk.core.ProtocolException; import dev.restate.sdk.core.ResolvedEndpointHandler; import dev.restate.sdk.core.RestateEndpoint; +import dev.restate.sdk.core.ServiceProtocol; import dev.restate.sdk.core.manifest.DeploymentManifestSchema; import dev.restate.sdk.version.Version; import io.netty.util.AsciiString; @@ -33,8 +34,6 @@ import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.impl.HttpServerRequestInternal; import java.net.URI; -import java.util.Objects; -import java.util.Optional; import java.util.concurrent.Executor; import java.util.regex.Pattern; import org.apache.logging.log4j.LogManager; @@ -45,11 +44,9 @@ class RequestHttpServerHandler implements Handler { private static final Logger LOG = LogManager.getLogger(RequestHttpServerHandler.class); - private static final AsciiString APPLICATION_RESTATE = AsciiString.cached("application/restate"); private static final AsciiString X_RESTATE_SERVER_KEY = AsciiString.cached("x-restate-server"); private static final AsciiString X_RESTATE_SERVER_VALUE = AsciiString.cached(Version.X_RESTATE_SERVER); - private static final ObjectMapper MANIFEST_OBJECT_MAPPER = new ObjectMapper(); private static final Pattern SLASH = Pattern.compile(Pattern.quote("/")); @@ -90,6 +87,27 @@ public void handle(HttpServerRequest request) { return; } + // check protocol version + final String protocolVersionString = request.getHeader(CONTENT_TYPE); + + final Protocol.ServiceProtocolVersion serviceProtocolVersion = + ServiceProtocol.parseServiceProtocolVersion(protocolVersionString); + + if (!ServiceProtocol.is_supported(serviceProtocolVersion)) { + final String errorMessage = + String.format( + "Service endpoint does not support the service protocol version '%s'.", + protocolVersionString); + LOG.warn(errorMessage); + request + .response() + .setStatusCode(UNSUPPORTED_MEDIA_TYPE.code()) + .putHeader(CONTENT_TYPE, "text/plain") + .putHeader(X_RESTATE_SERVER_KEY, X_RESTATE_SERVER_VALUE) + .end(errorMessage); + return; + } + // Parse request String[] pathSegments = SLASH.split(uri.getPath()); if (pathSegments.length < 3) { @@ -137,7 +155,9 @@ public void handle(HttpServerRequest request) { HttpServerResponse response = request.response(); response.setStatusCode(OK.code()); response - .putHeader(CONTENT_TYPE, APPLICATION_RESTATE) + .putHeader( + CONTENT_TYPE, + ServiceProtocol.serviceProtocolVersionToHeaderValue(serviceProtocolVersion)) .putHeader(X_RESTATE_SERVER_KEY, X_RESTATE_SERVER_VALUE); // This is No-op for HTTP2 response.setChunked(true); @@ -156,20 +176,37 @@ private Executor currentContextExecutor(Context currentContext) { } private void handleDiscoveryRequest(HttpServerRequest request) { - final String accept = request.getHeader(ACCEPT); - - final Discovery.ServiceDiscoveryProtocolVersion serviceDiscoveryProtocolVersion = selectSupportedServiceDiscoveryProtocolVersion(accept); - - if (serviceDiscoveryProtocolVersion == Discovery.ServiceDiscoveryProtocolVersion.V1) { + final String acceptVersionsString = request.getHeader(ACCEPT); + + final Discovery.ServiceDiscoveryProtocolVersion serviceDiscoveryProtocolVersion = + selectSupportedServiceDiscoveryProtocolVersion(acceptVersionsString); + + if (serviceDiscoveryProtocolVersion + == Discovery.ServiceDiscoveryProtocolVersion + .SERVICE_DISCOVERY_PROTOCOL_VERSION_UNSPECIFIED) { + final String errorMessage = + String.format( + "Unsupported service discovery protocol version: '%s'", acceptVersionsString); + LOG.warn(errorMessage); + request + .response() + .setStatusCode(UNSUPPORTED_MEDIA_TYPE.code()) + .putHeader(CONTENT_TYPE, "text/plain") + .putHeader(X_RESTATE_SERVER_KEY, X_RESTATE_SERVER_VALUE) + .end(errorMessage); + } else { // Compute response and write it back DeploymentManifestSchema response = this.restateEndpoint.handleDiscoveryRequest(); Buffer responseBuffer; try { - responseBuffer = Buffer.buffer(MANIFEST_OBJECT_MAPPER.writeValueAsBytes(response)); - } catch (JsonProcessingException e) { + responseBuffer = + Buffer.buffer( + new ServiceProtocol.DiscoveryResponseSerializer(serviceDiscoveryProtocolVersion) + .serialize(response)); + } catch (Exception e) { LOG.warn("Error when writing out the manifest POJO", e); - request.response().setStatusCode(INTERNAL_SERVER_ERROR.code()).end(); + request.response().setStatusCode(INTERNAL_SERVER_ERROR.code()).end(e.getMessage()); return; } @@ -177,48 +214,10 @@ private void handleDiscoveryRequest(HttpServerRequest request) { .response() .setStatusCode(OK.code()) .putHeader(X_RESTATE_SERVER_KEY, X_RESTATE_SERVER_VALUE) - .putHeader(CONTENT_TYPE, serviceDiscoveryProtocolVersionToContentType(serviceDiscoveryProtocolVersion)) + .putHeader( + CONTENT_TYPE, + serviceDiscoveryProtocolVersionToHeaderValue(serviceDiscoveryProtocolVersion)) .end(responseBuffer); - } else { - request.response().setStatusCode(UNSUPPORTED_MEDIA_TYPE.code()).setStatusMessage("Service endpoint does not support the accepted service discovery protocol versions.").end(); - } - } - - private static Discovery.ServiceDiscoveryProtocolVersion selectSupportedServiceDiscoveryProtocolVersion(String accept) { - // assume V1 in case nothing was set - if (accept == null || accept.isEmpty()) { - return Discovery.ServiceDiscoveryProtocolVersion.V1; - } - - final String[] supportedVersions = accept.split(","); - - Discovery.ServiceDiscoveryProtocolVersion maxVersion = Discovery.ServiceDiscoveryProtocolVersion.UNRECOGNIZED; - - for (String versionString: supportedVersions) { - final Optional optionalVersion = parseServiceDiscoveryProtocolVersion(versionString.trim()); - - if (optionalVersion.isPresent()) { - final Discovery.ServiceDiscoveryProtocolVersion version = optionalVersion.get(); - if (version.getNumber() > maxVersion.getNumber()) { - maxVersion = version; - } - } - } - - return maxVersion; - } - - private static Optional parseServiceDiscoveryProtocolVersion(String versionString) { - if (versionString.equals("application/vnd.restate.endpointmanifest.v1+json")) { - return Optional.of(Discovery.ServiceDiscoveryProtocolVersion.V1); - } - return Optional.empty(); - } - - private static String serviceDiscoveryProtocolVersionToContentType(Discovery.ServiceDiscoveryProtocolVersion version) { - if (Objects.requireNonNull(version) == Discovery.ServiceDiscoveryProtocolVersion.V1) { - return "application/vnd.restate.endpointmanifest.v1+json"; } - return ""; } } diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt index f309d3b9..512d7cc0 100644 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt +++ b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/HttpVertxTestExecutor.kt @@ -9,6 +9,8 @@ package dev.restate.sdk.http.vertx import com.google.protobuf.MessageLite +import dev.restate.generated.service.protocol.Protocol +import dev.restate.sdk.core.ServiceProtocol import dev.restate.sdk.core.TestDefinitions.TestDefinition import dev.restate.sdk.core.TestDefinitions.TestExecutor import io.vertx.core.Vertx @@ -57,7 +59,16 @@ class HttpVertxTestExecutor(private val vertx: Vertx) : TestExecutor { .coAwait() // Prepare request header and send them - request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") + request + .setChunked(true) + .putHeader( + HttpHeaders.CONTENT_TYPE, + ServiceProtocol.serviceProtocolVersionToHeaderValue( + Protocol.ServiceProtocolVersion.V1)) + .putHeader( + HttpHeaders.ACCEPT, + ServiceProtocol.serviceProtocolVersionToHeaderValue( + Protocol.ServiceProtocolVersion.V1)) request.sendHead().coAwait() launch { diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt index 4f121a70..ab5f74a1 100644 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt +++ b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt @@ -11,9 +11,11 @@ package dev.restate.sdk.http.vertx import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.ByteString import com.google.protobuf.MessageLite +import dev.restate.generated.service.discovery.Discovery import dev.restate.generated.service.protocol.Protocol.* import dev.restate.sdk.common.CoreSerdes import dev.restate.sdk.core.ProtoUtils.* +import dev.restate.sdk.core.ServiceProtocol import dev.restate.sdk.core.manifest.DeploymentManifestSchema import dev.restate.sdk.http.vertx.testservices.BlockingGreeter import dev.restate.sdk.http.vertx.testservices.greeter @@ -82,7 +84,11 @@ internal class RestateHttpEndpointTest { .coAwait() // Prepare request header - request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") + request + .setChunked(true) + .putHeader( + HttpHeaders.CONTENT_TYPE, + ServiceProtocol.serviceProtocolVersionToHeaderValue(ServiceProtocolVersion.V1)) // Send start message and PollInputStreamEntry request.write(encode(startMessage(1).build())) @@ -172,7 +178,14 @@ internal class RestateHttpEndpointTest { .coAwait() // Prepare request header - request.setChunked(true).putHeader(HttpHeaders.CONTENT_TYPE, "application/restate") + request + .setChunked(true) + .putHeader( + HttpHeaders.CONTENT_TYPE, + ServiceProtocol.serviceProtocolVersionToHeaderValue(ServiceProtocolVersion.V1)) + .putHeader( + HttpHeaders.ACCEPT, + ServiceProtocol.serviceProtocolVersionToHeaderValue(ServiceProtocolVersion.V1)) request.write(encode(startMessage(0).build())) val response = request.response().coAwait() @@ -200,6 +213,10 @@ internal class RestateHttpEndpointTest { // Send request val request = client.request(HttpMethod.GET, endpointPort, "localhost", "/discover").coAwait() + request.putHeader( + HttpHeaders.ACCEPT, + ServiceProtocol.serviceDiscoveryProtocolVersionToHeaderValue( + Discovery.ServiceDiscoveryProtocolVersion.V1)) request.end().coAwait() // Assert response @@ -207,7 +224,10 @@ internal class RestateHttpEndpointTest { // Response status and content type header assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code()) - assertThat(response.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/json") + assertThat(response.getHeader(HttpHeaders.CONTENT_TYPE)) + .isEqualTo( + ServiceProtocol.serviceDiscoveryProtocolVersionToHeaderValue( + Discovery.ServiceDiscoveryProtocolVersion.V1)) // Parse response val responseBody = response.body().coAwait() diff --git a/sdk-lambda/build.gradle.kts b/sdk-lambda/build.gradle.kts index fb05ac4e..e9a1c3da 100644 --- a/sdk-lambda/build.gradle.kts +++ b/sdk-lambda/build.gradle.kts @@ -13,10 +13,6 @@ dependencies { api(lambdaLibs.core) api(lambdaLibs.events) - // Jackson (we need it for the manifest) - implementation(platform(jacksonLibs.jackson.bom)) - implementation(jacksonLibs.jackson.databind) - implementation(platform(coreLibs.opentelemetry.bom)) implementation(coreLibs.opentelemetry.api) diff --git a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java index ec2abdcb..57e8bdad 100644 --- a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java +++ b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java @@ -8,16 +8,19 @@ // https://github.com/restatedev/sdk-java/blob/main/LICENSE package dev.restate.sdk.lambda; +import static dev.restate.sdk.core.ServiceProtocol.selectSupportedServiceDiscoveryProtocolVersion; +import static dev.restate.sdk.core.ServiceProtocol.serviceDiscoveryProtocolVersionToHeaderValue; import static dev.restate.sdk.lambda.LambdaFlowAdapters.*; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import dev.restate.generated.service.discovery.Discovery; +import dev.restate.generated.service.protocol.Protocol; import dev.restate.sdk.core.ProtocolException; import dev.restate.sdk.core.ResolvedEndpointHandler; import dev.restate.sdk.core.RestateEndpoint; +import dev.restate.sdk.core.ServiceProtocol; import dev.restate.sdk.core.manifest.DeploymentManifestSchema; import dev.restate.sdk.version.Version; import io.opentelemetry.api.OpenTelemetry; @@ -32,17 +35,11 @@ /** Restate Lambda Endpoint. */ public final class RestateLambdaEndpoint { - private static final ObjectMapper MANIFEST_OBJECT_MAPPER = new ObjectMapper(); - private static final Logger LOG = LogManager.getLogger(RestateLambdaEndpoint.class); private static final Pattern SLASH = Pattern.compile(Pattern.quote("/")); private static final String INVOKE_PATH_SEGMENT = "invoke"; private static final String DISCOVER_PATH = "/discover"; - private static final Map INVOKE_RESPONSE_HEADERS = - Map.of("content-type", "application/restate", "x-restate-server", Version.X_RESTATE_SERVER); - private static final Map DISCOVER_RESPONSE_HEADERS = - Map.of("content-type", "application/json", "x-restate-server", Version.X_RESTATE_SERVER); private static TextMapGetter> OTEL_HEADERS_GETTER = new TextMapGetter<>() { @@ -83,7 +80,7 @@ public APIGatewayProxyResponseEvent handleRequest( : input.getPath(); if (path.endsWith(DISCOVER_PATH)) { - return this.handleDiscovery(); + return this.handleDiscovery(input.getHeaders().get("accept")); } return this.handleInvoke(input); @@ -92,6 +89,27 @@ public APIGatewayProxyResponseEvent handleRequest( // --- Invoke request private APIGatewayProxyResponseEvent handleInvoke(APIGatewayProxyRequestEvent input) { + // check protocol version + final String protocolVersionString = input.getHeaders().get("content-type"); + + final Protocol.ServiceProtocolVersion serviceProtocolVersion = + ServiceProtocol.parseServiceProtocolVersion(protocolVersionString); + + if (!ServiceProtocol.is_supported(serviceProtocolVersion)) { + final String errorMessage = + String.format( + "Service endpoint does not support the service protocol version '%s'.", + protocolVersionString); + LOG.warn(errorMessage); + + final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); + response.setStatusCode(415); + response.setHeaders( + Map.of("content-type", "text/plain", "x-restate-server", Version.X_RESTATE_SERVER)); + response.setBody(errorMessage); + return response; + } + // Parse request String[] pathSegments = SLASH.split(input.getPath()); if (pathSegments.length < 3 @@ -155,7 +173,12 @@ private APIGatewayProxyResponseEvent handleInvoke(APIGatewayProxyRequestEvent in ThreadContext.clearAll(); final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); - response.setHeaders(INVOKE_RESPONSE_HEADERS); + response.setHeaders( + Map.of( + "content-type", + ServiceProtocol.serviceProtocolVersionToHeaderValue(serviceProtocolVersion), + "x-restate-server", + Version.X_RESTATE_SERVER)); response.setIsBase64Encoded(true); response.setStatusCode(200); response.setBody(Base64.getEncoder().encodeToString(responseBody)); @@ -164,26 +187,51 @@ private APIGatewayProxyResponseEvent handleInvoke(APIGatewayProxyRequestEvent in // --- Service discovery - private APIGatewayProxyResponseEvent handleDiscovery() { - // Compute response and write it back - DeploymentManifestSchema responseManifest = this.restateEndpoint.handleDiscoveryRequest(); - byte[] serializedManifest; - try { - serializedManifest = MANIFEST_OBJECT_MAPPER.writeValueAsBytes(responseManifest); - } catch (JsonProcessingException e) { - LOG.warn("Error when writing out the manifest POJO", e); + private APIGatewayProxyResponseEvent handleDiscovery(String acceptVersionsString) { + final Discovery.ServiceDiscoveryProtocolVersion serviceDiscoveryProtocolVersion = + selectSupportedServiceDiscoveryProtocolVersion(acceptVersionsString); + + if (serviceDiscoveryProtocolVersion + == Discovery.ServiceDiscoveryProtocolVersion + .SERVICE_DISCOVERY_PROTOCOL_VERSION_UNSPECIFIED) { + final String errorMessage = + String.format( + "Unsupported service discovery protocol version: '%s'", acceptVersionsString); + LOG.warn(errorMessage); + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); + response.setStatusCode(415); + response.setHeaders( + Map.of("content-type", "text/plain", "x-restate-server", Version.X_RESTATE_SERVER)); + response.setBody(errorMessage); + return response; + } else { + // Compute response and write it back + DeploymentManifestSchema responseManifest = this.restateEndpoint.handleDiscoveryRequest(); + byte[] serializedManifest; + try { + serializedManifest = + new ServiceProtocol.DiscoveryResponseSerializer(serviceDiscoveryProtocolVersion) + .serialize(responseManifest); + } catch (Exception e) { + LOG.warn("Error when writing out the manifest POJO", e); + final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); + response.setStatusCode(500); + response.setBody(e.getMessage()); + return response; + } + final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); - response.setStatusCode(500); - response.setBody(e.getMessage()); + response.setHeaders( + Map.of( + "content-type", + serviceDiscoveryProtocolVersionToHeaderValue(serviceDiscoveryProtocolVersion), + "x-restate-server", + Version.X_RESTATE_SERVER)); + response.setIsBase64Encoded(true); + response.setStatusCode(200); + response.setBody(Base64.getEncoder().encodeToString(serializedManifest)); return response; } - - final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); - response.setHeaders(DISCOVER_RESPONSE_HEADERS); - response.setIsBase64Encoded(true); - response.setStatusCode(200); - response.setBody(Base64.getEncoder().encodeToString(serializedManifest)); - return response; } // --- Utils diff --git a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java index 5a5a8f80..bb63de3b 100644 --- a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java +++ b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java @@ -19,8 +19,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.protobuf.ByteString; import com.google.protobuf.MessageLite; +import dev.restate.generated.service.discovery.Discovery; import dev.restate.generated.service.protocol.Protocol; import dev.restate.sdk.core.ProtoUtils; +import dev.restate.sdk.core.ServiceProtocol; import dev.restate.sdk.core.manifest.DeploymentManifestSchema; import dev.restate.sdk.core.manifest.Service; import dev.restate.sdk.lambda.testservices.JavaCounterDefinitions; @@ -42,7 +44,11 @@ public void testInvoke(String serviceName) throws IOException { // Mock request APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); - request.setHeaders(Map.of("content-type", "application/restate")); + request.setHeaders( + Map.of( + "content-type", + ServiceProtocol.serviceProtocolVersionToHeaderValue( + Protocol.ServiceProtocolVersion.V1))); request.setPath("/a/path/prefix/invoke/" + serviceName + "/get"); request.setHttpMethod("POST"); request.setIsBase64Encoded(true); @@ -63,7 +69,11 @@ public void testInvoke(String serviceName) throws IOException { // Assert response assertThat(response.getStatusCode()).isEqualTo(200); - assertThat(response.getHeaders()).containsEntry("content-type", "application/restate"); + assertThat(response.getHeaders()) + .containsEntry( + "content-type", + ServiceProtocol.serviceProtocolVersionToHeaderValue( + Protocol.ServiceProtocolVersion.V1)); assertThat(response.getIsBase64Encoded()).isTrue(); assertThat(response.getBody()) .asBase64Decoded() @@ -82,13 +92,22 @@ public void testDiscovery() throws IOException { // Mock request APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); request.setPath("/a/path/prefix/discover"); + request.setHeaders( + Map.of( + "accept", + ServiceProtocol.serviceDiscoveryProtocolVersionToHeaderValue( + Discovery.ServiceDiscoveryProtocolVersion.V1))); // Send request APIGatewayProxyResponseEvent response = handler.handleRequest(request, mockContext()); // Assert response assertThat(response.getStatusCode()).isEqualTo(200); - assertThat(response.getHeaders()).containsEntry("content-type", "application/json"); + assertThat(response.getHeaders()) + .containsEntry( + "content-type", + ServiceProtocol.serviceDiscoveryProtocolVersionToHeaderValue( + Discovery.ServiceDiscoveryProtocolVersion.V1)); assertThat(response.getIsBase64Encoded()).isTrue(); byte[] decodedStringResponse = Base64.getDecoder().decode(response.getBody()); // Compute response and write it back From ecde439d67f5361e87440aa4515bcc6eb02e9d36 Mon Sep 17 00:00:00 2001 From: Till Rohrmann Date: Thu, 16 May 2024 22:25:28 +0200 Subject: [PATCH 5/6] Remove the outdated version protocol check We are no longer sending the protocol version via the message headers of the start message. This commit removes this logic. This fixes #317. --- .../sdk/core/InvocationStateMachine.java | 1 - .../dev/restate/sdk/core/MessageHeader.java | 19 ------------------- .../restate/sdk/core/MessageHeaderTest.java | 12 ------------ .../java/dev/restate/sdk/core/ProtoUtils.java | 5 +---- 4 files changed, 1 insertion(+), 36 deletions(-) diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationStateMachine.java b/sdk-core/src/main/java/dev/restate/sdk/core/InvocationStateMachine.java index 728e2c48..d3428164 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/InvocationStateMachine.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/InvocationStateMachine.java @@ -135,7 +135,6 @@ public void onNext(InvocationFlow.InvocationInput invocationInput) { MessageLite msg = invocationInput.message(); LOG.trace("Received input message {} {}", msg.getClass(), msg); if (this.invocationState == InvocationState.WAITING_START) { - MessageHeader.checkProtocolVersion(invocationInput.header()); this.onStartMessage(msg); } else if (msg instanceof Protocol.CompletionMessage) { // We check the instance rather than the state, because the user code might still be diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/MessageHeader.java b/sdk-core/src/main/java/dev/restate/sdk/core/MessageHeader.java index 292526a3..b8ec492a 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/MessageHeader.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/MessageHeader.java @@ -13,9 +13,6 @@ public class MessageHeader { - static final short SUPPORTED_PROTOCOL_VERSION = 2; - - static final short VERSION_MASK = 0x03FF; static final short DONE_FLAG = 0x0001; static final int REQUIRES_ACK_FLAG = 0x8000; @@ -101,20 +98,4 @@ public static MessageHeader fromMessage(MessageLite msg) { // Messages with no flags return new MessageHeader(MessageType.fromMessage(msg), 0, msg.getSerializedSize()); } - - public static void checkProtocolVersion(MessageHeader header) { - if (header.type != MessageType.StartMessage) { - throw new IllegalStateException("Expected StartMessage, got " + header.type); - } - - short version = (short) (header.flags & VERSION_MASK); - if (version != SUPPORTED_PROTOCOL_VERSION) { - throw new IllegalStateException( - "Unsupported protocol version " - + version - + ", only version " - + SUPPORTED_PROTOCOL_VERSION - + " is supported"); - } - } } diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java b/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java index 09b0dc02..f46cef6f 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/MessageHeaderTest.java @@ -24,16 +24,4 @@ void requiresAckFlag() { .encode()) .isEqualTo(0x0C01_8001_0000_0002L); } - - @Test - void checkProtocolVersion() { - int unknownVersion = Integer.MAX_VALUE & MessageHeader.VERSION_MASK; - assertThatThrownBy( - () -> - MessageHeader.checkProtocolVersion( - new MessageHeader(MessageType.StartMessage, unknownVersion, 0))) - .hasMessage( - "Unsupported protocol version %d, only version %d is supported", - unknownVersion, MessageHeader.SUPPORTED_PROTOCOL_VERSION); - } } diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/ProtoUtils.java b/sdk-core/src/test/java/dev/restate/sdk/core/ProtoUtils.java index 74003448..0338758f 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/ProtoUtils.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/ProtoUtils.java @@ -30,10 +30,7 @@ public class ProtoUtils { */ public static MessageHeader headerFromMessage(MessageLite msg) { if (msg instanceof Protocol.StartMessage) { - return new MessageHeader( - MessageType.StartMessage, - MessageHeader.SUPPORTED_PROTOCOL_VERSION, - msg.getSerializedSize()); + return new MessageHeader(MessageType.StartMessage, 0, msg.getSerializedSize()); } else if (msg instanceof Protocol.CompletionMessage) { return new MessageHeader(MessageType.CompletionMessage, (short) 0, msg.getSerializedSize()); } From 32e0b7d1cde3882d61c1d88540decbc149a30ab4 Mon Sep 17 00:00:00 2001 From: Till Rohrmann Date: Fri, 17 May 2024 11:20:26 +0200 Subject: [PATCH 6/6] Replace Deployment* with EndpointManifestSchema --- ...entManifest.java => EndpointManifest.java} | 14 ++++++------ .../dev/restate/sdk/core/RestateEndpoint.java | 18 +++++++-------- .../dev/restate/sdk/core/ServiceProtocol.java | 15 ++++++++----- .../dev/restate/sdk/core/AssertUtils.java | 22 +++++++++---------- .../core/ComponentDiscoveryHandlerTest.java | 14 ++++++------ .../restate/sdk/core/MockMultiThreaded.java | 4 ++-- .../restate/sdk/core/MockSingleThread.java | 4 ++-- .../http/vertx/RequestHttpServerHandler.java | 4 ++-- .../vertx/RestateHttpEndpointBuilder.java | 4 ++-- .../sdk/http/vertx/RestateHttpEndpointTest.kt | 6 ++--- .../sdk/lambda/RestateLambdaEndpoint.java | 4 ++-- .../lambda/RestateLambdaEndpointBuilder.java | 4 ++-- .../restate/sdk/lambda/LambdaHandlerTest.java | 6 ++--- 13 files changed, 61 insertions(+), 58 deletions(-) rename sdk-core/src/main/java/dev/restate/sdk/core/{DeploymentManifest.java => EndpointManifest.java} (89%) diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java b/sdk-core/src/main/java/dev/restate/sdk/core/EndpointManifest.java similarity index 89% rename from sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java rename to sdk-core/src/main/java/dev/restate/sdk/core/EndpointManifest.java index ad01e64e..06a34ff7 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/DeploymentManifest.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/EndpointManifest.java @@ -20,17 +20,17 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -final class DeploymentManifest { +final class EndpointManifest { private static final Input EMPTY_INPUT = new Input(); private static final Output EMPTY_OUTPUT = new Output().withSetContentTypeIfEmpty(false); - private final DeploymentManifestSchema manifest; + private final EndpointManifestSchema manifest; - public DeploymentManifest( - DeploymentManifestSchema.ProtocolMode protocolMode, Stream> components) { + public EndpointManifest( + EndpointManifestSchema.ProtocolMode protocolMode, Stream> components) { this.manifest = - new DeploymentManifestSchema() + new EndpointManifestSchema() .withMinProtocolVersion(MIN_SERVICE_PROTOCOL_VERSION.getNumber()) .withMaxProtocolVersion(MAX_SERVICE_PROTOCOL_VERSION.getNumber()) .withProtocolMode(protocolMode) @@ -43,12 +43,12 @@ public DeploymentManifest( .withTy(convertServiceType(svc.getServiceType())) .withHandlers( svc.getHandlers().stream() - .map(DeploymentManifest::convertHandler) + .map(EndpointManifest::convertHandler) .collect(Collectors.toList()))) .collect(Collectors.toList())); } - public DeploymentManifestSchema manifest() { + public EndpointManifestSchema manifest() { return this.manifest; } diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java b/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java index f481933c..624afa21 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/RestateEndpoint.java @@ -12,7 +12,7 @@ import dev.restate.sdk.common.BindableServiceFactory; import dev.restate.sdk.common.syscalls.HandlerDefinition; import dev.restate.sdk.common.syscalls.ServiceDefinition; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import dev.restate.sdk.core.manifest.Service; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; @@ -34,10 +34,10 @@ public class RestateEndpoint { private final Map> services; private final Tracer tracer; private final RequestIdentityVerifier requestIdentityVerifier; - private final DeploymentManifest deploymentManifest; + private final EndpointManifest deploymentManifest; private RestateEndpoint( - DeploymentManifestSchema.ProtocolMode protocolMode, + EndpointManifestSchema.ProtocolMode protocolMode, Map> services, Tracer tracer, RequestIdentityVerifier requestIdentityVerifier) { @@ -45,7 +45,7 @@ private RestateEndpoint( this.tracer = tracer; this.requestIdentityVerifier = requestIdentityVerifier; this.deploymentManifest = - new DeploymentManifest(protocolMode, services.values().stream().map(c -> c.service)); + new EndpointManifest(protocolMode, services.values().stream().map(c -> c.service)); this.logCreation(); } @@ -99,8 +99,8 @@ public ResolvedEndpointHandler resolve( return new ResolvedEndpointHandlerImpl(stateMachine, handler, svc.options, syscallExecutor); } - public DeploymentManifestSchema handleDiscoveryRequest() { - DeploymentManifestSchema response = this.deploymentManifest.manifest(); + public EndpointManifestSchema handleDiscoveryRequest() { + EndpointManifestSchema response = this.deploymentManifest.manifest(); LOG.info( "Replying to discovery request with services [{}]", response.getServices().stream().map(Service::getName).collect(Collectors.joining(","))); @@ -113,18 +113,18 @@ private void logCreation() { // -- Builder - public static Builder newBuilder(DeploymentManifestSchema.ProtocolMode protocolMode) { + public static Builder newBuilder(EndpointManifestSchema.ProtocolMode protocolMode) { return new Builder(protocolMode); } public static class Builder { private final List> services = new ArrayList<>(); - private final DeploymentManifestSchema.ProtocolMode protocolMode; + private final EndpointManifestSchema.ProtocolMode protocolMode; private RequestIdentityVerifier requestIdentityVerifier; private Tracer tracer = OpenTelemetry.noop().getTracer("NOOP"); - public Builder(DeploymentManifestSchema.ProtocolMode protocolMode) { + public Builder(EndpointManifestSchema.ProtocolMode protocolMode) { this.protocolMode = protocolMode; } diff --git a/sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java b/sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java index ce0eaf79..ae815620 100644 --- a/sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java +++ b/sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java @@ -11,7 +11,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import dev.restate.generated.service.discovery.Discovery; import dev.restate.generated.service.protocol.Protocol; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import java.util.Objects; import java.util.Optional; @@ -40,7 +40,8 @@ public static String serviceProtocolVersionToHeaderValue( if (Objects.requireNonNull(version) == Protocol.ServiceProtocolVersion.V1) { return "application/vnd.restate.invocation.v1"; } - throw new IllegalArgumentException(String.format("Service protocol version '%s' has no header value", version.getNumber())); + throw new IllegalArgumentException( + String.format("Service protocol version '%s' has no header value", version.getNumber())); } public static boolean is_supported(Protocol.ServiceProtocolVersion serviceProtocolVersion) { @@ -60,8 +61,8 @@ public static boolean is_supported( * Selects the highest supported service protocol version from a list of supported versions. * * @param acceptedVersionsString A comma-separated list of accepted service protocol versions. - * @return The highest supported service protocol version, otherwise {@link - * Protocol.ServiceProtocolVersion.SERVICE_PROTOCOL_VERSION_UNSPECIFIED} + * @return The highest supported service protocol version, otherwise + * Protocol.ServiceProtocolVersion.SERVICE_PROTOCOL_VERSION_UNSPECIFIED */ public static Discovery.ServiceDiscoveryProtocolVersion selectSupportedServiceDiscoveryProtocolVersion(String acceptedVersionsString) { @@ -105,7 +106,9 @@ public static String serviceDiscoveryProtocolVersionToHeaderValue( if (Objects.requireNonNull(version) == Discovery.ServiceDiscoveryProtocolVersion.V1) { return "application/vnd.restate.endpointmanifest.v1+json"; } - throw new IllegalArgumentException(String.format("Service discovery protocol version '%s' has no header value", version.getNumber())); + throw new IllegalArgumentException( + String.format( + "Service discovery protocol version '%s' has no header value", version.getNumber())); } public static class DiscoveryResponseSerializer { @@ -122,7 +125,7 @@ public DiscoveryResponseSerializer( this.serviceDiscoveryProtocolVersion = serviceDiscoveryProtocolVersion; } - public byte[] serialize(DeploymentManifestSchema response) throws Exception { + public byte[] serialize(EndpointManifestSchema response) throws Exception { if (this.serviceDiscoveryProtocolVersion == Discovery.ServiceDiscoveryProtocolVersion.V1) { return MANIFEST_OBJECT_MAPPER.writeValueAsBytes(response); } diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/AssertUtils.java b/sdk-core/src/test/java/dev/restate/sdk/core/AssertUtils.java index 327130b0..5b648bee 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/AssertUtils.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/AssertUtils.java @@ -17,7 +17,7 @@ import dev.restate.generated.service.protocol.Protocol; import dev.restate.sdk.common.BindableService; import dev.restate.sdk.common.TerminalException; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import dev.restate.sdk.core.manifest.Handler; import dev.restate.sdk.core.manifest.Service; import java.util.Arrays; @@ -68,10 +68,10 @@ public static Consumer protocolExceptionErrorMessage(int co .startsWith(ProtocolException.class.getCanonicalName())); } - public static DeploymentManifestSchemaAssert assertThatDiscovery(Object... services) { - return new DeploymentManifestSchemaAssert( - new DeploymentManifest( - DeploymentManifestSchema.ProtocolMode.BIDI_STREAM, + public static EndpointManifestSchemaAssert assertThatDiscovery(Object... services) { + return new EndpointManifestSchemaAssert( + new EndpointManifest( + EndpointManifestSchema.ProtocolMode.BIDI_STREAM, Arrays.stream(services) .flatMap( svc -> { @@ -85,14 +85,14 @@ public static DeploymentManifestSchemaAssert assertThatDiscovery(Object... servi .stream(); })) .manifest(), - DeploymentManifestSchemaAssert.class); + EndpointManifestSchemaAssert.class); } - public static class DeploymentManifestSchemaAssert - extends AbstractObjectAssert { - public DeploymentManifestSchemaAssert( - DeploymentManifestSchema deploymentManifestSchema, Class selfType) { - super(deploymentManifestSchema, selfType); + public static class EndpointManifestSchemaAssert + extends AbstractObjectAssert { + public EndpointManifestSchemaAssert( + EndpointManifestSchema endpointManifestSchema, Class selfType) { + super(endpointManifestSchema, selfType); } public ServiceAssert extractingService(String service) { diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java b/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java index 351981b9..bb27580c 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/ComponentDiscoveryHandlerTest.java @@ -16,8 +16,7 @@ import dev.restate.sdk.common.syscalls.HandlerDefinition; import dev.restate.sdk.common.syscalls.HandlerSpecification; import dev.restate.sdk.common.syscalls.ServiceDefinition; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema.ProtocolMode; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import dev.restate.sdk.core.manifest.Service; import java.util.List; import java.util.stream.Stream; @@ -27,9 +26,9 @@ class ComponentDiscoveryHandlerTest { @Test void handleWithMultipleServices() { - DeploymentManifest deploymentManifest = - new DeploymentManifest( - ProtocolMode.REQUEST_RESPONSE, + EndpointManifest deploymentManifest = + new EndpointManifest( + EndpointManifestSchema.ProtocolMode.REQUEST_RESPONSE, Stream.of( ServiceDefinition.of( "MyGreeter", @@ -40,9 +39,10 @@ void handleWithMultipleServices() { "greet", HandlerType.EXCLUSIVE, CoreSerdes.VOID, CoreSerdes.VOID), null))))); - DeploymentManifestSchema manifest = deploymentManifest.manifest(); + EndpointManifestSchema manifest = deploymentManifest.manifest(); assertThat(manifest.getServices()).extracting(Service::getName).containsOnly("MyGreeter"); - assertThat(manifest.getProtocolMode()).isEqualTo(ProtocolMode.REQUEST_RESPONSE); + assertThat(manifest.getProtocolMode()) + .isEqualTo(EndpointManifestSchema.ProtocolMode.REQUEST_RESPONSE); } } diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/MockMultiThreaded.java b/sdk-core/src/test/java/dev/restate/sdk/core/MockMultiThreaded.java index 135fe1b0..25c37baa 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/MockMultiThreaded.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/MockMultiThreaded.java @@ -13,7 +13,7 @@ import com.google.protobuf.MessageLite; import dev.restate.sdk.common.BindableService; import dev.restate.sdk.common.syscalls.ServiceDefinition; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import java.time.Duration; import java.util.List; import java.util.concurrent.Executor; @@ -47,7 +47,7 @@ public void executeTest(TestDefinitions.TestDefinition definition) { // Prepare server RestateEndpoint.Builder builder = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.BIDI_STREAM) + RestateEndpoint.newBuilder(EndpointManifestSchema.ProtocolMode.BIDI_STREAM) .bind(serviceDefinition.get(0), bindableService.options()); RestateEndpoint server = builder.build(); diff --git a/sdk-core/src/test/java/dev/restate/sdk/core/MockSingleThread.java b/sdk-core/src/test/java/dev/restate/sdk/core/MockSingleThread.java index e6db1e6a..e8ef1112 100644 --- a/sdk-core/src/test/java/dev/restate/sdk/core/MockSingleThread.java +++ b/sdk-core/src/test/java/dev/restate/sdk/core/MockSingleThread.java @@ -15,7 +15,7 @@ import dev.restate.sdk.common.syscalls.ServiceDefinition; import dev.restate.sdk.core.TestDefinitions.TestDefinition; import dev.restate.sdk.core.TestDefinitions.TestExecutor; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import java.time.Duration; import java.util.List; import org.apache.logging.log4j.ThreadContext; @@ -45,7 +45,7 @@ public void executeTest(TestDefinition definition) { // Prepare server RestateEndpoint.Builder builder = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.BIDI_STREAM) + RestateEndpoint.newBuilder(EndpointManifestSchema.ProtocolMode.BIDI_STREAM) .bind(serviceDefinition.get(0), bindableService.options()); RestateEndpoint server = builder.build(); diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java index 73f4c39c..dd398ebe 100644 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java +++ b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RequestHttpServerHandler.java @@ -20,7 +20,7 @@ import dev.restate.sdk.core.ResolvedEndpointHandler; import dev.restate.sdk.core.RestateEndpoint; import dev.restate.sdk.core.ServiceProtocol; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import dev.restate.sdk.version.Version; import io.netty.util.AsciiString; import io.opentelemetry.api.OpenTelemetry; @@ -196,7 +196,7 @@ private void handleDiscoveryRequest(HttpServerRequest request) { .end(errorMessage); } else { // Compute response and write it back - DeploymentManifestSchema response = this.restateEndpoint.handleDiscoveryRequest(); + EndpointManifestSchema response = this.restateEndpoint.handleDiscoveryRequest(); Buffer responseBuffer; try { diff --git a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java index 97de236b..063a1541 100644 --- a/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java +++ b/sdk-http-vertx/src/main/java/dev/restate/sdk/http/vertx/RestateHttpEndpointBuilder.java @@ -12,7 +12,7 @@ import dev.restate.sdk.common.BindableService; import dev.restate.sdk.common.syscalls.ServiceDefinition; import dev.restate.sdk.core.RestateEndpoint; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import io.opentelemetry.api.OpenTelemetry; import io.vertx.core.AsyncResult; import io.vertx.core.Vertx; @@ -43,7 +43,7 @@ public class RestateHttpEndpointBuilder { private final Vertx vertx; private final RestateEndpoint.Builder endpointBuilder = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.BIDI_STREAM); + RestateEndpoint.newBuilder(EndpointManifestSchema.ProtocolMode.BIDI_STREAM); private OpenTelemetry openTelemetry = OpenTelemetry.noop(); private HttpServerOptions options = new HttpServerOptions() diff --git a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt index ab5f74a1..d9f33812 100644 --- a/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt +++ b/sdk-http-vertx/src/test/kotlin/dev/restate/sdk/http/vertx/RestateHttpEndpointTest.kt @@ -16,7 +16,7 @@ import dev.restate.generated.service.protocol.Protocol.* import dev.restate.sdk.common.CoreSerdes import dev.restate.sdk.core.ProtoUtils.* import dev.restate.sdk.core.ServiceProtocol -import dev.restate.sdk.core.manifest.DeploymentManifestSchema +import dev.restate.sdk.core.manifest.EndpointManifestSchema import dev.restate.sdk.http.vertx.testservices.BlockingGreeter import dev.restate.sdk.http.vertx.testservices.greeter import io.netty.handler.codec.http.HttpResponseStatus @@ -232,8 +232,8 @@ internal class RestateHttpEndpointTest { // Parse response val responseBody = response.body().coAwait() // Compute response and write it back - val discoveryResponse: DeploymentManifestSchema = - ObjectMapper().readValue(responseBody.bytes, DeploymentManifestSchema::class.java) + val discoveryResponse: EndpointManifestSchema = + ObjectMapper().readValue(responseBody.bytes, EndpointManifestSchema::class.java) assertThat(discoveryResponse.services) .map { it.name } diff --git a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java index 57e8bdad..fd0833e8 100644 --- a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java +++ b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpoint.java @@ -21,7 +21,7 @@ import dev.restate.sdk.core.ResolvedEndpointHandler; import dev.restate.sdk.core.RestateEndpoint; import dev.restate.sdk.core.ServiceProtocol; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import dev.restate.sdk.version.Version; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.propagation.TextMapGetter; @@ -206,7 +206,7 @@ private APIGatewayProxyResponseEvent handleDiscovery(String acceptVersionsString return response; } else { // Compute response and write it back - DeploymentManifestSchema responseManifest = this.restateEndpoint.handleDiscoveryRequest(); + EndpointManifestSchema responseManifest = this.restateEndpoint.handleDiscoveryRequest(); byte[] serializedManifest; try { serializedManifest = diff --git a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java index 52a407ab..7c35a77e 100644 --- a/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java +++ b/sdk-lambda/src/main/java/dev/restate/sdk/lambda/RestateLambdaEndpointBuilder.java @@ -12,14 +12,14 @@ import dev.restate.sdk.common.BindableService; import dev.restate.sdk.common.syscalls.ServiceDefinition; import dev.restate.sdk.core.RestateEndpoint; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import io.opentelemetry.api.OpenTelemetry; /** Endpoint builder for a Restate AWS Lambda Endpoint, to serve Restate service. */ public final class RestateLambdaEndpointBuilder { private final RestateEndpoint.Builder restateEndpoint = - RestateEndpoint.newBuilder(DeploymentManifestSchema.ProtocolMode.REQUEST_RESPONSE); + RestateEndpoint.newBuilder(EndpointManifestSchema.ProtocolMode.REQUEST_RESPONSE); private OpenTelemetry openTelemetry = OpenTelemetry.noop(); /** diff --git a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java index bb63de3b..05a65af7 100644 --- a/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java +++ b/sdk-lambda/src/test/java/dev/restate/sdk/lambda/LambdaHandlerTest.java @@ -23,7 +23,7 @@ import dev.restate.generated.service.protocol.Protocol; import dev.restate.sdk.core.ProtoUtils; import dev.restate.sdk.core.ServiceProtocol; -import dev.restate.sdk.core.manifest.DeploymentManifestSchema; +import dev.restate.sdk.core.manifest.EndpointManifestSchema; import dev.restate.sdk.core.manifest.Service; import dev.restate.sdk.lambda.testservices.JavaCounterDefinitions; import dev.restate.sdk.lambda.testservices.MyServicesHandler; @@ -111,8 +111,8 @@ public void testDiscovery() throws IOException { assertThat(response.getIsBase64Encoded()).isTrue(); byte[] decodedStringResponse = Base64.getDecoder().decode(response.getBody()); // Compute response and write it back - DeploymentManifestSchema discoveryResponse = - new ObjectMapper().readValue(decodedStringResponse, DeploymentManifestSchema.class); + EndpointManifestSchema discoveryResponse = + new ObjectMapper().readValue(decodedStringResponse, EndpointManifestSchema.class); assertThat(discoveryResponse.getServices()) .map(Service::getName)