diff --git a/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandler.java b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandler.java new file mode 100644 index 000000000..7c4277049 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2025 The Dapr 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.retryhandler; + +import io.dapr.workflows.WorkflowContext; +import io.dapr.workflows.WorkflowTaskRetryContext; +import io.dapr.workflows.WorkflowTaskRetryHandler; +import org.slf4j.Logger; + +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +public class DemoRetryHandler implements WorkflowTaskRetryHandler { + + @Override + public boolean handle(WorkflowTaskRetryContext retryContext) { + WorkflowContext workflowContext = retryContext.getWorkflowContext(); + Logger logger = retryContext.getWorkflowContext().getLogger(); + Object input = retryContext.getInput(); + String taskName = retryContext.getTaskName(); + + if(taskName.equalsIgnoreCase(FailureActivity.class.getName())) { + logger.info("FailureActivity Input: {}", input); + Instant timestampInput = (Instant) input; + // Add a second to ensure, it is 100% passed the time to success + Instant timeToSuccess = timestampInput.plusSeconds(FailureActivity.TIME_TO_SUCCESS + 1); + long timeToWait = timestampInput.until(timeToSuccess, TimeUnit.SECONDS.toChronoUnit()); + + logger.info("Waiting {} seconds before retrying.", timeToWait); + workflowContext.createTimer(Duration.ofSeconds(timeToWait)).await(); + logger.info("Send request to FailureActivity"); + } + + return true; + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandlerClient.java b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandlerClient.java new file mode 100644 index 000000000..e233cc537 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandlerClient.java @@ -0,0 +1,47 @@ +/* + * Copyright 2025 The Dapr 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.retryhandler; + +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; + +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeoutException; + +public class DemoRetryHandlerClient { + /** + * The main method to start the client. + * + * @param args Input arguments (unused). + * @throws InterruptedException If program has been interrupted. + */ + public static void main(String[] args) { + try (DaprWorkflowClient client = new DaprWorkflowClient()) { + String instanceId = client.scheduleNewWorkflow(DemoRetryWorkflow.class); + System.out.printf("Started a new external-event workflow with instance ID: %s%n", instanceId); + + // Block until the orchestration completes. Then print the final status, which includes the output. + WorkflowInstanceStatus workflowInstanceStatus = client.waitForInstanceCompletion( + instanceId, + Duration.ofSeconds(30), + true); + + System.out.printf("workflow instance with ID: %s completed with result: %s%n", instanceId, + workflowInstanceStatus.readOutputAs(Instant.class)); + } catch (TimeoutException | InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandlerWorker.java b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandlerWorker.java new file mode 100644 index 000000000..3a0f215fa --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryHandlerWorker.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Dapr 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.retryhandler; + +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; + +public class DemoRetryHandlerWorker { + /** + * The main method of this app. + * + * @param args The port the app will listen on. + * @throws Exception An Exception. + */ + public static void main(String[] args) throws Exception { + // Register the Workflow with the builder. + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(DemoRetryWorkflow.class); + builder.registerActivity(FailureActivity.class); + + // Build and then start the workflow runtime pulling and executing tasks + WorkflowRuntime runtime = builder.build(); + System.out.println("Start workflow runtime"); + runtime.start(); + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryWorkflow.java new file mode 100644 index 000000000..f6629634f --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/DemoRetryWorkflow.java @@ -0,0 +1,43 @@ +/* + * Copyright 2025 The Dapr 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.retryhandler; + +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import io.dapr.workflows.WorkflowTaskOptions; +import io.dapr.workflows.WorkflowTaskRetryHandler; +import org.slf4j.Logger; + +import java.time.Instant; + +public class DemoRetryWorkflow implements Workflow { + + @Override + public WorkflowStub create() { + return context -> { + Logger logger = context.getLogger(); + logger.info("Starting RetryWorkflow: {}", context.getName()); + + WorkflowTaskRetryHandler retryHandler = new DemoRetryHandler(); + WorkflowTaskOptions taskOptions = new WorkflowTaskOptions(retryHandler); + + logger.info("RetryWorkflow is calling Activity: {}", FailureActivity.class.getName()); + Instant currentTime = context.getCurrentInstant(); + Instant result = context.callActivity(FailureActivity.class.getName(), currentTime, taskOptions, Instant.class).await(); + + logger.info("RetryWorkflow finished with: {}", result); + context.complete(result); + }; + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/retryhandler/FailureActivity.java b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/FailureActivity.java new file mode 100644 index 000000000..e5ac60b76 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/retryhandler/FailureActivity.java @@ -0,0 +1,44 @@ +/* + * Copyright 2025 The Dapr 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.examples.workflows.retryhandler; + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Instant; + +public class FailureActivity implements WorkflowActivity { + + private static final Logger LOGGER = LoggerFactory.getLogger(FailureActivity.class); + public static final long TIME_TO_SUCCESS = 10; + + @Override + public Object run(WorkflowActivityContext ctx) { + LOGGER.info("Starting Activity: {}", ctx.getName()); + + Instant timestamp = ctx.getInput(Instant.class); + + LOGGER.info("Input timestamp: {}", timestamp); + if(timestamp.plusSeconds(TIME_TO_SUCCESS).isBefore(Instant.now())) { + LOGGER.info("Completing Activity: {}", ctx.getName()); + return Instant.now(); + } + + LOGGER.info("Throwing exception for Activity: {}", ctx.getName()); + + throw new RuntimeException("Failure!"); + } +}