From 989b9e1b3ee8a109a443885f6af66b0d513cb613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Tue, 26 Dec 2023 11:02:26 +0100 Subject: [PATCH 01/18] task interaction --- .../samples/taskinteraction/README.md | 44 ++++++ .../samples/taskinteraction/Task.java | 72 +++++++++ .../samples/taskinteraction/TaskActivity.java | 28 ++++ .../taskinteraction/TaskActivityImpl.java | 35 +++++ .../samples/taskinteraction/TaskClient.java | 34 ++++ .../samples/taskinteraction/TaskService.java | 129 +++++++++++++++ .../samples/taskinteraction/TaskWorkflow.java | 30 ++++ .../taskinteraction/TaskWorkflowImpl.java | 73 +++++++++ .../taskinteraction/client/ListOpenTasks.java | 54 +++++++ .../taskinteraction/client/StartWorkflow.java | 54 +++++++ .../taskinteraction/client/UpdateTask.java | 55 +++++++ .../taskinteraction/worker/Worker.java | 49 ++++++ .../taskinteraction/TaskWorkflowImplTest.java | 148 ++++++++++++++++++ 13 files changed, 805 insertions(+) create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/README.md create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/Task.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/TaskActivity.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/TaskActivityImpl.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/client/ListOpenTasks.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/client/StartWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/worker/Worker.java create mode 100644 core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/README.md b/core/src/main/java/io/temporal/samples/taskinteraction/README.md new file mode 100644 index 00000000..9ad580f3 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/README.md @@ -0,0 +1,44 @@ +# Demo tasks interaction + +This example demonstrate a generic implementation for tasks interaction in Temporal. + +The basic implementation consist on three parts: +- One activity (or local activity) that send the request (create a task). +- Block the workflow execution `Workflow.await` awaiting a Signal. +- The workflow will eventually receive a signal that unblocks it. + +Additionally, the example allows to track task state (PENDING, STARTED, COMPLETED...) and +list open task. + +> If the client can not send a Signal to the workflow execution, steps 2 and 3 can be replaced by an activity +that polls using one of [these three strategies](../polling). + +## Run the sample + +- Start the worker + +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.worker.Worker +``` + +- Schedule workflow execution + +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.StartWorkflow +``` + +- List task + +This process will query the workflow execution every 10 seconds, and print open tasks (state != COMPLETED). +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.ListOpenTasks + + + +``` +- Update task + +Update one of the open task to the next state (PENDING -> STARTED -> COMPLETED) +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.ListOpenTasks +``` diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java new file mode 100644 index 00000000..8b7e711d --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class Task { + + private String token; + private Object data; + private STATE state; + + public Task() {} + + public Task(String token) { + this.token = token; + this.state = STATE.PENDING; + } + + public String getToken() { + return token; + } + + public void setData(Object data) { + this.data = data; + } + + public T result(Class tClass) { + return (T) data; + } + + public void setState(STATE state) { + this.state = state; + } + + public STATE getState() { + return state; + } + + @JsonIgnore + public boolean isCompleted() { + return STATE.COMPLETED == this.state; + } + + @Override + public String toString() { + return "Task{" + "token='" + token + '\'' + ", data=" + data + ", state=" + state + '}'; + } + + public enum STATE { + PENDING, + STARTED, + COMPLETED + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivity.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivity.java new file mode 100644 index 00000000..a9e12619 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivity.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import io.temporal.activity.ActivityInterface; + +@ActivityInterface +public interface TaskActivity { + + String createTask(String task); +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivityImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivityImpl.java new file mode 100644 index 00000000..51ea7133 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivityImpl.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +public class TaskActivityImpl implements TaskActivity { + @Override + public String createTask(String task) { + + // Simulating delay in task creation + try { + Thread.sleep(500); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + return "activity created"; + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java new file mode 100644 index 00000000..32ebd1ba --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import java.util.List; + +/** Interface used to dynamically register signal and query handlers from the interceptor. */ +public interface TaskClient { + + @SignalMethod + void updateTask(TaskService.TaskRequest task); + + @QueryMethod + List getOpenTasks(); +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java new file mode 100644 index 00000000..cb7c3abd --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.temporal.workflow.CompletablePromise; +import io.temporal.workflow.Promise; +import io.temporal.workflow.Workflow; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; + +public class TaskService { + + private final Map tasks = Collections.synchronizedMap(new HashMap<>()); + private final Map> pendingPromises = + Collections.synchronizedMap(new HashMap<>()); + + // Exposes signal and query methods that + // allow us to interact with the workflow execution + private final TaskClient listener = + new TaskClient() { + + @Override + public void updateTask(TaskRequest taskRequest) { + + final String token = taskRequest.getToken(); + final String data = taskRequest.getData(); + tasks.get(token).setData(data); + + final Task t = tasks.get(token); + + t.setState(taskRequest.state); + tasks.put(t.getToken(), t); + + logger.info("Task updated: " + t); + + if (taskRequest.state == Task.STATE.COMPLETED) { + final CompletablePromise completablePromise = pendingPromises.get(token); + completablePromise.complete((R) data); + } + } + + @Override + public List getOpenTasks() { + return tasks.values().stream().filter(t -> !t.isCompleted()).collect(Collectors.toList()); + } + }; + + public TaskService() { + Workflow.registerListener(listener); + } + + private final Logger logger = Workflow.getLogger(TaskService.class); + + public R executeTask(Callback callback, String token) { + return executeTaskAsync(callback, token).get(); + } + + public Promise executeTaskAsync(Callback callback, String token) { + + final Task task = new Task(token); + logger.info("Before creating task : " + task); + tasks.put(token, task); + callback.execute(); + logger.info("Task created: " + task); + + final CompletablePromise promise = Workflow.newPromise(); + pendingPromises.put(token, promise); + + return promise; + } + + public List getOpenTasks() { + return listener.getOpenTasks(); + } + + public interface Callback { + T execute(); + } + + public static class TaskRequest { + + private Task.STATE state; + private String data; + private String token; + + public TaskRequest() {} + + public TaskRequest(Task.STATE state, String data, String token) { + this.state = state; + this.data = data; + this.token = token; + } + + @JsonIgnore + public boolean isCompleted() { + return this.state == Task.STATE.COMPLETED; + } + + public String getToken() { + return token; + } + + public String getData() { + return data; + } + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflow.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflow.java new file mode 100644 index 00000000..aa8c8fe1 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflow.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface TaskWorkflow { + + @WorkflowMethod + void execute(); +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java new file mode 100644 index 00000000..a7ab6dad --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import io.temporal.activity.ActivityOptions; +import io.temporal.workflow.Promise; +import io.temporal.workflow.Workflow; +import java.time.Duration; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; + +public class TaskWorkflowImpl implements TaskWorkflow { + + private final Logger logger = Workflow.getLogger(TaskWorkflowImpl.class); + + private final TaskService taskService = new TaskService<>(); + + private final TaskActivity activity = + Workflow.newActivityStub( + TaskActivity.class, + ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(5)).build()); + + @Override + public void execute() { + + // Schedule two "tasks" in parallel. The last parameter is the token the client needs + // to change the task state, and ultimately to complete the task + logger.info("About to create async tasks"); + final Promise task1 = + taskService.executeTaskAsync(() -> activity.createTask("TODO 1"), taskToken()); + final Promise task2 = + taskService.executeTaskAsync(() -> activity.createTask("TODO 2"), taskToken()); + + logger.info("Awaiting for two tasks to get completed"); + // Block execution until both tasks complete + Promise.allOf(Arrays.asList(task1, task2)).get(); + logger.info("Two tasks completed"); + + logger.info("About to create one blocking task"); + // Blocking invocation + taskService.executeTask(() -> activity.createTask("TODO 3"), taskToken()); + logger.info("Task completed"); + logger.info("Completing workflow"); + } + + private final AtomicInteger atomicInteger = new AtomicInteger(1); + + private String taskToken() { + return Workflow.getInfo().getWorkflowId() + + "-" + + Workflow.currentTimeMillis() + + "-" + + atomicInteger.getAndIncrement(); + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/client/ListOpenTasks.java b/core/src/main/java/io/temporal/samples/taskinteraction/client/ListOpenTasks.java new file mode 100644 index 00000000..11bcedad --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/client/ListOpenTasks.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction.client; + +import static io.temporal.samples.taskinteraction.client.StartWorkflow.WORKFLOW_ID; + +import io.temporal.client.WorkflowClient; +import io.temporal.samples.taskinteraction.Task; +import io.temporal.samples.taskinteraction.TaskClient; +import io.temporal.serviceclient.WorkflowServiceStubs; +import java.util.List; + +public class ListOpenTasks { + + public static void main(String[] args) throws InterruptedException { + + final WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + final WorkflowClient client = WorkflowClient.newInstance(service); + + final TaskClient taskClient = client.newWorkflowStub(TaskClient.class, WORKFLOW_ID); + + List openTasks = taskClient.getOpenTasks(); + while (openTasks.size() > 0) { + + System.out.println("\nOpen tasks:[" + openTasks.size() + "]"); + openTasks.forEach( + t -> { + System.out.println(" " + t); + }); + + Thread.sleep(10000); + openTasks = taskClient.getOpenTasks(); + } + + System.exit(0); + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/client/StartWorkflow.java b/core/src/main/java/io/temporal/samples/taskinteraction/client/StartWorkflow.java new file mode 100644 index 00000000..694b0730 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/client/StartWorkflow.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction.client; + +import static io.temporal.samples.taskinteraction.worker.Worker.TASK_QUEUE; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.samples.taskinteraction.TaskWorkflow; +import io.temporal.serviceclient.WorkflowServiceStubs; + +public class StartWorkflow { + + static final String WORKFLOW_ID = "TaskWorkflow"; + + public static void main(String[] args) { + + final WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + final WorkflowClient client = WorkflowClient.newInstance(service); + + final TaskWorkflow workflow = + client.newWorkflowStub( + TaskWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(WORKFLOW_ID) + .setTaskQueue(TASK_QUEUE) + .build()); + + System.out.println("Starting workflow " + WORKFLOW_ID); + + // Execute workflow waiting for it to complete. + workflow.execute(); + + System.out.println("Workflow completed"); + System.exit(0); + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java b/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java new file mode 100644 index 00000000..f31d5bee --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction.client; + +import static io.temporal.samples.taskinteraction.client.StartWorkflow.WORKFLOW_ID; + +import io.temporal.client.WorkflowClient; +import io.temporal.samples.taskinteraction.Task; +import io.temporal.samples.taskinteraction.TaskClient; +import io.temporal.samples.taskinteraction.TaskService; +import io.temporal.serviceclient.WorkflowServiceStubs; +import java.util.Arrays; +import java.util.List; + +public class UpdateTask { + + public static void main(String[] args) { + + final WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + final WorkflowClient client = WorkflowClient.newInstance(service); + + final TaskClient taskClient = client.newWorkflowStub(TaskClient.class, WORKFLOW_ID); + + final List openTasks = taskClient.getOpenTasks(); + + final Task randomOpenTask = openTasks.get(0); + final List states = Arrays.asList(Task.STATE.values()); + + final Task.STATE nextState = states.get(states.indexOf(randomOpenTask.getState()) + 1); + + System.out.println("\nUpdating task " + randomOpenTask + " to " + nextState); + taskClient.updateTask( + new TaskService.TaskRequest( + nextState, "Updated to " + nextState, randomOpenTask.getToken())); + + System.exit(0); + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/worker/Worker.java b/core/src/main/java/io/temporal/samples/taskinteraction/worker/Worker.java new file mode 100644 index 00000000..f11ed94c --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/worker/Worker.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction.worker; + +import io.temporal.client.WorkflowClient; +import io.temporal.samples.taskinteraction.TaskActivityImpl; +import io.temporal.samples.taskinteraction.TaskWorkflowImpl; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.WorkerFactory; +import io.temporal.worker.WorkerFactoryOptions; + +public class Worker { + + public static final String TASK_QUEUE = "TaskWorkflowImplTaskQueue"; + + public static void main(String[] args) { + + final WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + final WorkflowClient client = WorkflowClient.newInstance(service); + + final WorkerFactoryOptions factoryOptions = + WorkerFactoryOptions.newBuilder().validateAndBuildWithDefaults(); + + final WorkerFactory factory = WorkerFactory.newInstance(client, factoryOptions); + + io.temporal.worker.Worker worker = factory.newWorker(TASK_QUEUE); + worker.registerWorkflowImplementationTypes(TaskWorkflowImpl.class); + worker.registerActivitiesImplementations(new TaskActivityImpl()); + + factory.start(); + } +} diff --git a/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java b/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java new file mode 100644 index 00000000..cd42cfb6 --- /dev/null +++ b/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.temporal.api.common.v1.WorkflowExecution; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowStub; +import io.temporal.common.interceptors.*; +import io.temporal.testing.TestWorkflowRule; +import io.temporal.worker.WorkerFactoryOptions; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Rule; +import org.junit.Test; + +public class TaskWorkflowImplTest { + + final CompletableFuture[] completedActivities = + new CompletableFuture[] {new CompletableFuture<>()}; + + @Rule + public TestWorkflowRule testWorkflowRule = + TestWorkflowRule.newBuilder() + .setWorkerFactoryOptions( + WorkerFactoryOptions.newBuilder() + .setWorkerInterceptors(new MyWorkerInterceptor(completedActivities)) + .validateAndBuildWithDefaults()) + .setWorkflowTypes(TaskWorkflowImpl.class) + .setDoNotStart(true) + .build(); + + @Test + public void testRunWorkflow() throws InterruptedException, ExecutionException { + + final TaskActivity activities = mock(TaskActivity.class); + when(activities.createTask(any())).thenReturn("done"); + testWorkflowRule.getWorker().registerActivitiesImplementations(activities); + + testWorkflowRule.getTestEnvironment().start(); + + // Get stub to the dynamically registered interface + TaskWorkflow workflow = testWorkflowRule.newWorkflowStub(TaskWorkflow.class); + + // start workflow execution + WorkflowExecution execution = WorkflowClient.start(workflow::execute); + + final TaskClient taskClient = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub(TaskClient.class, execution.getWorkflowId()); + + // Wait for the activity to get executed two times. Two tasks created + completedActivities[0].get(); + + final List asyncTasksStarted = taskClient.getOpenTasks(); + assertEquals(2, asyncTasksStarted.size()); + // Change tasks state, not completing them yet + changeTaskState(taskClient, asyncTasksStarted, Task.STATE.STARTED); + + // The two tasks are still open, lets complete them + final List asyncTasksPending = taskClient.getOpenTasks(); + assertEquals(2, asyncTasksPending.size()); + changeTaskState(taskClient, asyncTasksPending, Task.STATE.COMPLETED); + + // Wait for the activity to get executed for the third time. One more task gets created + completedActivities[0].get(); + + final List syncTask = taskClient.getOpenTasks(); + assertEquals(1, syncTask.size()); + changeTaskState(taskClient, syncTask, Task.STATE.STARTED); + changeTaskState(taskClient, syncTask, Task.STATE.COMPLETED); + + // Workflow completes + final WorkflowStub untyped = + testWorkflowRule.getWorkflowClient().newUntypedWorkflowStub(execution.getWorkflowId()); + untyped.getResult(String.class); + } + + private static void changeTaskState(TaskClient client, List tasks, Task.STATE state) { + tasks.forEach( + t -> { + client.updateTask( + new TaskService.TaskRequest(state, "Changing state to: " + state, t.getToken())); + }); + } + + private final AtomicInteger integer = new AtomicInteger(0); + + private class OnActivityCompleteInterceptor extends ActivityInboundCallsInterceptorBase { + private CompletableFuture[] completableFuture; + + public OnActivityCompleteInterceptor( + final ActivityInboundCallsInterceptor next, + final CompletableFuture[] completableFuture) { + super(next); + this.completableFuture = completableFuture; + } + + @Override + public ActivityOutput execute(final ActivityInput input) { + final ActivityOutput execute = super.execute(input); + integer.getAndIncrement(); + if (Arrays.asList(2, 3).contains(integer.get())) { + completableFuture[0].complete(null); + completableFuture[0] = new CompletableFuture<>(); + } + return execute; + } + } + + private class MyWorkerInterceptor extends WorkerInterceptorBase { + + private CompletableFuture[] completableFuture; + + public MyWorkerInterceptor(final CompletableFuture[] completableFuture) { + this.completableFuture = completableFuture; + } + + public ActivityInboundCallsInterceptor interceptActivity(ActivityInboundCallsInterceptor next) { + return new OnActivityCompleteInterceptor(next, this.completableFuture); + } + } +} From 54fcd368a56ffdb6f4b1f05176e91ad729bcb8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Thu, 4 Jan 2024 11:03:38 +0100 Subject: [PATCH 02/18] using UpperCamelCase for the enum name and lowerCamelCase for values. --- .../temporal/samples/taskinteraction/Task.java | 18 +++++++++--------- .../samples/taskinteraction/TaskService.java | 8 ++++---- .../taskinteraction/client/UpdateTask.java | 4 ++-- .../taskinteraction/TaskWorkflowImplTest.java | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java index 8b7e711d..ac62240b 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java @@ -25,13 +25,13 @@ public class Task { private String token; private Object data; - private STATE state; + private State state; public Task() {} public Task(String token) { this.token = token; - this.state = STATE.PENDING; + this.state = State.pending; } public String getToken() { @@ -46,17 +46,17 @@ public T result(Class tClass) { return (T) data; } - public void setState(STATE state) { + public void setState(State state) { this.state = state; } - public STATE getState() { + public State getState() { return state; } @JsonIgnore public boolean isCompleted() { - return STATE.COMPLETED == this.state; + return State.completed == this.state; } @Override @@ -64,9 +64,9 @@ public String toString() { return "Task{" + "token='" + token + '\'' + ", data=" + data + ", state=" + state + '}'; } - public enum STATE { - PENDING, - STARTED, - COMPLETED + public enum State { + pending, + started, + completed } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java index cb7c3abd..b12c8d81 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java @@ -55,7 +55,7 @@ public void updateTask(TaskRequest taskRequest) { logger.info("Task updated: " + t); - if (taskRequest.state == Task.STATE.COMPLETED) { + if (taskRequest.state == Task.State.completed) { final CompletablePromise completablePromise = pendingPromises.get(token); completablePromise.complete((R) data); } @@ -101,13 +101,13 @@ public interface Callback { public static class TaskRequest { - private Task.STATE state; + private Task.State state; private String data; private String token; public TaskRequest() {} - public TaskRequest(Task.STATE state, String data, String token) { + public TaskRequest(Task.State state, String data, String token) { this.state = state; this.data = data; this.token = token; @@ -115,7 +115,7 @@ public TaskRequest(Task.STATE state, String data, String token) { @JsonIgnore public boolean isCompleted() { - return this.state == Task.STATE.COMPLETED; + return this.state == Task.State.completed; } public String getToken() { diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java b/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java index f31d5bee..aeaa8209 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java @@ -41,9 +41,9 @@ public static void main(String[] args) { final List openTasks = taskClient.getOpenTasks(); final Task randomOpenTask = openTasks.get(0); - final List states = Arrays.asList(Task.STATE.values()); + final List states = Arrays.asList(Task.State.values()); - final Task.STATE nextState = states.get(states.indexOf(randomOpenTask.getState()) + 1); + final Task.State nextState = states.get(states.indexOf(randomOpenTask.getState()) + 1); System.out.println("\nUpdating task " + randomOpenTask + " to " + nextState); taskClient.updateTask( diff --git a/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java b/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java index cd42cfb6..5cf855d8 100644 --- a/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java +++ b/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java @@ -80,20 +80,20 @@ public void testRunWorkflow() throws InterruptedException, ExecutionException { final List asyncTasksStarted = taskClient.getOpenTasks(); assertEquals(2, asyncTasksStarted.size()); // Change tasks state, not completing them yet - changeTaskState(taskClient, asyncTasksStarted, Task.STATE.STARTED); + changeTaskState(taskClient, asyncTasksStarted, Task.State.started); // The two tasks are still open, lets complete them final List asyncTasksPending = taskClient.getOpenTasks(); assertEquals(2, asyncTasksPending.size()); - changeTaskState(taskClient, asyncTasksPending, Task.STATE.COMPLETED); + changeTaskState(taskClient, asyncTasksPending, Task.State.completed); // Wait for the activity to get executed for the third time. One more task gets created completedActivities[0].get(); final List syncTask = taskClient.getOpenTasks(); assertEquals(1, syncTask.size()); - changeTaskState(taskClient, syncTask, Task.STATE.STARTED); - changeTaskState(taskClient, syncTask, Task.STATE.COMPLETED); + changeTaskState(taskClient, syncTask, Task.State.started); + changeTaskState(taskClient, syncTask, Task.State.completed); // Workflow completes final WorkflowStub untyped = @@ -101,7 +101,7 @@ public void testRunWorkflow() throws InterruptedException, ExecutionException { untyped.getResult(String.class); } - private static void changeTaskState(TaskClient client, List tasks, Task.STATE state) { + private static void changeTaskState(TaskClient client, List tasks, Task.State state) { tasks.forEach( t -> { client.updateTask( From bb771c34767a69ce791fad85c730c9abb5bdd6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Thu, 4 Jan 2024 11:08:57 +0100 Subject: [PATCH 03/18] changed from synchronizedMap to Map, as synchronization is not needed in workflow code --- .../java/io/temporal/samples/taskinteraction/TaskService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java index b12c8d81..a25abe70 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java @@ -32,7 +32,7 @@ public class TaskService { - private final Map tasks = Collections.synchronizedMap(new HashMap<>()); + private final Map tasks = new HashMap<>(); private final Map> pendingPromises = Collections.synchronizedMap(new HashMap<>()); From f9b081093c351833ab51a08d329008a2aad763a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Thu, 4 Jan 2024 17:12:37 +0100 Subject: [PATCH 04/18] moved taskToken mothod to a class --- .../taskinteraction/TaskWorkflowImpl.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java index a7ab6dad..e353e965 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java @@ -24,7 +24,6 @@ import io.temporal.workflow.Workflow; import java.time.Duration; import java.util.Arrays; -import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; public class TaskWorkflowImpl implements TaskWorkflow { @@ -40,14 +39,15 @@ public class TaskWorkflowImpl implements TaskWorkflow { @Override public void execute() { + final TaskToken taskToken = new TaskToken(); // Schedule two "tasks" in parallel. The last parameter is the token the client needs // to change the task state, and ultimately to complete the task logger.info("About to create async tasks"); final Promise task1 = - taskService.executeTaskAsync(() -> activity.createTask("TODO 1"), taskToken()); + taskService.executeTaskAsync(() -> activity.createTask("TODO 1"), taskToken.getNext()); final Promise task2 = - taskService.executeTaskAsync(() -> activity.createTask("TODO 2"), taskToken()); + taskService.executeTaskAsync(() -> activity.createTask("TODO 2"), taskToken.getNext()); logger.info("Awaiting for two tasks to get completed"); // Block execution until both tasks complete @@ -56,18 +56,22 @@ public void execute() { logger.info("About to create one blocking task"); // Blocking invocation - taskService.executeTask(() -> activity.createTask("TODO 3"), taskToken()); + taskService.executeTask(() -> activity.createTask("TODO 3"), taskToken.getNext()); logger.info("Task completed"); logger.info("Completing workflow"); } - private final AtomicInteger atomicInteger = new AtomicInteger(1); + private static class TaskToken { - private String taskToken() { - return Workflow.getInfo().getWorkflowId() - + "-" - + Workflow.currentTimeMillis() - + "-" - + atomicInteger.getAndIncrement(); + private int taskToken = 1; + + public String getNext() { + + return Workflow.getInfo().getWorkflowId() + + "-" + + Workflow.currentTimeMillis() + + "-" + + taskToken++; + } } } From 8f3e298c9d7648963628918c96eb329fdb1ba9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Thu, 4 Jan 2024 17:20:31 +0100 Subject: [PATCH 05/18] removed executeTaskAsync method --- .../temporal/samples/taskinteraction/TaskService.java | 7 +------ .../samples/taskinteraction/TaskWorkflowImpl.java | 10 ++++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java index a25abe70..d2f31574 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import io.temporal.workflow.CompletablePromise; -import io.temporal.workflow.Promise; import io.temporal.workflow.Workflow; import java.util.Collections; import java.util.HashMap; @@ -74,10 +73,6 @@ public TaskService() { private final Logger logger = Workflow.getLogger(TaskService.class); public R executeTask(Callback callback, String token) { - return executeTaskAsync(callback, token).get(); - } - - public Promise executeTaskAsync(Callback callback, String token) { final Task task = new Task(token); logger.info("Before creating task : " + task); @@ -88,7 +83,7 @@ public Promise executeTaskAsync(Callback callback, String token) { final CompletablePromise promise = Workflow.newPromise(); pendingPromises.put(token, promise); - return promise; + return promise.get(); } public List getOpenTasks() { diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java index e353e965..1ed6decb 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java @@ -20,6 +20,7 @@ package io.temporal.samples.taskinteraction; import io.temporal.activity.ActivityOptions; +import io.temporal.workflow.Async; import io.temporal.workflow.Promise; import io.temporal.workflow.Workflow; import java.time.Duration; @@ -45,9 +46,14 @@ public void execute() { // to change the task state, and ultimately to complete the task logger.info("About to create async tasks"); final Promise task1 = - taskService.executeTaskAsync(() -> activity.createTask("TODO 1"), taskToken.getNext()); + Async.function( + () -> + taskService.executeTask(() -> activity.createTask("TODO 1"), taskToken.getNext())); + final Promise task2 = - taskService.executeTaskAsync(() -> activity.createTask("TODO 2"), taskToken.getNext()); + Async.function( + () -> + taskService.executeTask(() -> activity.createTask("TODO 2"), taskToken.getNext())); logger.info("Awaiting for two tasks to get completed"); // Block execution until both tasks complete From 08ecfa098c7e908a19f4b7980568fb211d0cb70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Thu, 4 Jan 2024 23:02:04 +0100 Subject: [PATCH 06/18] stored task data as a serialized type, instead of object --- .../samples/taskinteraction/Task.java | 32 ++++++++++++++++--- .../samples/taskinteraction/TaskClient.java | 2 +- .../samples/taskinteraction/TaskService.java | 22 ++++++------- .../taskinteraction/client/UpdateTask.java | 4 +-- .../taskinteraction/TaskWorkflowImplTest.java | 3 +- 5 files changed, 44 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java index ac62240b..e7f396a3 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java @@ -24,7 +24,7 @@ public class Task { private String token; - private Object data; + private TaskData data; private State state; public Task() {} @@ -38,12 +38,12 @@ public String getToken() { return token; } - public void setData(Object data) { + public void setData(TaskData data) { this.data = data; } - public T result(Class tClass) { - return (T) data; + public TaskData getData() { + return data; } public void setState(State state) { @@ -69,4 +69,28 @@ public enum State { started, completed } + + public static class TaskData { + private String value; + + public TaskData() {} + + public TaskData(final String value) { + + this.value = value; + } + + public void setValue(final String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return "TaskData{" + "value='" + value + '\'' + '}'; + } + } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java index 32ebd1ba..9eb251a4 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java @@ -27,7 +27,7 @@ public interface TaskClient { @SignalMethod - void updateTask(TaskService.TaskRequest task); + void updateTask(TaskService.UpdateTaskRequest task); @QueryMethod List getOpenTasks(); diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java index d2f31574..61c249ee 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java @@ -41,22 +41,22 @@ public class TaskService { new TaskClient() { @Override - public void updateTask(TaskRequest taskRequest) { + public void updateTask(UpdateTaskRequest updateTaskRequest) { - final String token = taskRequest.getToken(); - final String data = taskRequest.getData(); + final String token = updateTaskRequest.getToken(); + final Task.TaskData data = updateTaskRequest.getData(); tasks.get(token).setData(data); final Task t = tasks.get(token); - t.setState(taskRequest.state); + t.setState(updateTaskRequest.state); tasks.put(t.getToken(), t); logger.info("Task updated: " + t); - if (taskRequest.state == Task.State.completed) { + if (updateTaskRequest.state == Task.State.completed) { final CompletablePromise completablePromise = pendingPromises.get(token); - completablePromise.complete((R) data); + completablePromise.complete((R) data.getValue()); } } @@ -94,15 +94,15 @@ public interface Callback { T execute(); } - public static class TaskRequest { + public static class UpdateTaskRequest { private Task.State state; - private String data; + private Task.TaskData data; private String token; - public TaskRequest() {} + public UpdateTaskRequest() {} - public TaskRequest(Task.State state, String data, String token) { + public UpdateTaskRequest(Task.State state, Task.TaskData data, String token) { this.state = state; this.data = data; this.token = token; @@ -117,7 +117,7 @@ public String getToken() { return token; } - public String getData() { + public Task.TaskData getData() { return data; } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java b/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java index aeaa8209..5878b121 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java @@ -47,8 +47,8 @@ public static void main(String[] args) { System.out.println("\nUpdating task " + randomOpenTask + " to " + nextState); taskClient.updateTask( - new TaskService.TaskRequest( - nextState, "Updated to " + nextState, randomOpenTask.getToken())); + new TaskService.UpdateTaskRequest( + nextState, new Task.TaskData("Updated to " + nextState), randomOpenTask.getToken())); System.exit(0); } diff --git a/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java b/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java index 5cf855d8..617d3007 100644 --- a/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java +++ b/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java @@ -105,7 +105,8 @@ private static void changeTaskState(TaskClient client, List tasks, Task.St tasks.forEach( t -> { client.updateTask( - new TaskService.TaskRequest(state, "Changing state to: " + state, t.getToken())); + new TaskService.UpdateTaskRequest( + state, new Task.TaskData("Changing state to: " + state), t.getToken())); }); } From 3b83278e0042b7252f44e20448bb48afab6c5349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Tue, 9 Jan 2024 14:44:32 +0100 Subject: [PATCH 07/18] removed ListOpenTasks --- .../samples/taskinteraction/README.md | 18 +++---- .../samples/taskinteraction/TaskService.java | 4 -- .../taskinteraction/client/ListOpenTasks.java | 54 ------------------- 3 files changed, 6 insertions(+), 70 deletions(-) delete mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/client/ListOpenTasks.java diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/README.md b/core/src/main/java/io/temporal/samples/taskinteraction/README.md index 9ad580f3..365b6c6b 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/README.md +++ b/core/src/main/java/io/temporal/samples/taskinteraction/README.md @@ -7,8 +7,7 @@ The basic implementation consist on three parts: - Block the workflow execution `Workflow.await` awaiting a Signal. - The workflow will eventually receive a signal that unblocks it. -Additionally, the example allows to track task state (PENDING, STARTED, COMPLETED...) and -list open task. +Additionally, the example allows to track task state (PENDING, STARTED, COMPLETED...). > If the client can not send a Signal to the workflow execution, steps 2 and 3 can be replaced by an activity that polls using one of [these three strategies](../polling). @@ -27,18 +26,13 @@ that polls using one of [these three strategies](../polling). ./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.StartWorkflow ``` -- List task - -This process will query the workflow execution every 10 seconds, and print open tasks (state != COMPLETED). -```bash -./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.ListOpenTasks - - - -``` - Update task Update one of the open task to the next state (PENDING -> STARTED -> COMPLETED) ```bash -./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.ListOpenTasks +./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.UpdateTask ``` + +The workflow has three task, each task has three different states and is created in PENDING state. +You will have to run this class six times to move each task from PENDING to STARTED and to COMPLETED. +Once the last task is completed the workflow completes. diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java index 61c249ee..570a3f79 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java @@ -86,10 +86,6 @@ public R executeTask(Callback callback, String token) { return promise.get(); } - public List getOpenTasks() { - return listener.getOpenTasks(); - } - public interface Callback { T execute(); } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/client/ListOpenTasks.java b/core/src/main/java/io/temporal/samples/taskinteraction/client/ListOpenTasks.java deleted file mode 100644 index 11bcedad..00000000 --- a/core/src/main/java/io/temporal/samples/taskinteraction/client/ListOpenTasks.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved - * - * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Modifications copyright (C) 2017 Uber Technologies, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not - * use this file except in compliance with the License. A copy of the License is - * located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction.client; - -import static io.temporal.samples.taskinteraction.client.StartWorkflow.WORKFLOW_ID; - -import io.temporal.client.WorkflowClient; -import io.temporal.samples.taskinteraction.Task; -import io.temporal.samples.taskinteraction.TaskClient; -import io.temporal.serviceclient.WorkflowServiceStubs; -import java.util.List; - -public class ListOpenTasks { - - public static void main(String[] args) throws InterruptedException { - - final WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); - final WorkflowClient client = WorkflowClient.newInstance(service); - - final TaskClient taskClient = client.newWorkflowStub(TaskClient.class, WORKFLOW_ID); - - List openTasks = taskClient.getOpenTasks(); - while (openTasks.size() > 0) { - - System.out.println("\nOpen tasks:[" + openTasks.size() + "]"); - openTasks.forEach( - t -> { - System.out.println(" " + t); - }); - - Thread.sleep(10000); - openTasks = taskClient.getOpenTasks(); - } - - System.exit(0); - } -} From 6a1dbeaecbf910d910a47e44bcff24af8505d8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Fri, 22 Mar 2024 09:50:36 +0100 Subject: [PATCH 08/18] wip --- build.gradle | 2 +- .../temporal/samples/hello/HelloActivity.java | 37 +-- .../samples/hello/HelloActivityCancel.java | 238 ++++++++++++++++++ .../temporal/samples/hello/HelloSignal.java | 58 +++-- .../temporal/samples/hello/HelloUpdate.java | 2 +- .../samples/listworkflows/Starter.java | 1 + core/src/main/resources/logback.xml | 4 +- .../TestFailedWorkflowExceptionTypes.java | 85 +++++++ 8 files changed, 369 insertions(+), 58 deletions(-) create mode 100644 core/src/main/java/io/temporal/samples/hello/HelloActivityCancel.java create mode 100644 core/src/test/java/io/temporal/samples/hello/TestFailedWorkflowExceptionTypes.java diff --git a/build.gradle b/build.gradle index 16e719fd..2af4a583 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ subprojects { apply plugin: 'com.diffplug.spotless' compileJava { - options.compilerArgs << "-Werror" +// options.compilerArgs << "-Werror" } java { diff --git a/core/src/main/java/io/temporal/samples/hello/HelloActivity.java b/core/src/main/java/io/temporal/samples/hello/HelloActivity.java index 9e46dfe6..8dfb0941 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloActivity.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloActivity.java @@ -23,7 +23,6 @@ import io.temporal.activity.ActivityMethod; import io.temporal.activity.ActivityOptions; import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowOptions; import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.worker.Worker; import io.temporal.worker.WorkerFactory; @@ -79,7 +78,7 @@ public interface GreetingActivities { // Define your activity method which can be called during workflow execution @ActivityMethod(name = "greet") - String composeGreeting(String greeting, String name); + int composeGreeting(String greeting, String name); } // Define the workflow implementation which implements our getGreeting workflow method. @@ -103,7 +102,12 @@ public static class GreetingWorkflowImpl implements GreetingWorkflow { @Override public String getGreeting(String name) { // This is a blocking call that returns only after the activity has completed. - return activities.composeGreeting("Hello", name); + + activities.composeGreeting("Hello", name); + + Workflow.sleep(Duration.ofSeconds(20)); + + return "hello"; } } @@ -112,9 +116,10 @@ static class GreetingActivitiesImpl implements GreetingActivities { private static final Logger log = LoggerFactory.getLogger(GreetingActivitiesImpl.class); @Override - public String composeGreeting(String greeting, String name) { + public int composeGreeting(String greeting, String name) { log.info("Composing greeting..."); - return greeting + " " + name + "!"; + + return 1; } } @@ -161,27 +166,5 @@ public static void main(String[] args) { * The started workers then start polling for workflows and activities. */ factory.start(); - - // Create the workflow client stub. It is used to start our workflow execution. - GreetingWorkflow workflow = - client.newWorkflowStub( - GreetingWorkflow.class, - WorkflowOptions.newBuilder() - .setWorkflowId(WORKFLOW_ID) - .setTaskQueue(TASK_QUEUE) - .build()); - - /* - * Execute our workflow and wait for it to complete. The call to our getGreeting method is - * synchronous. - * - * See {@link io.temporal.samples.hello.HelloSignal} for an example of starting workflow - * without waiting synchronously for its result. - */ - String greeting = workflow.getGreeting("World"); - - // Display workflow execution results - System.out.println(greeting); - System.exit(0); } } diff --git a/core/src/main/java/io/temporal/samples/hello/HelloActivityCancel.java b/core/src/main/java/io/temporal/samples/hello/HelloActivityCancel.java new file mode 100644 index 00000000..c478e1f1 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/hello/HelloActivityCancel.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.hello; + +import io.temporal.activity.*; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.common.RetryOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.workflow.*; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HelloActivityCancel { + + static final String TASK_QUEUE = "HelloActivityTaskQueue"; + + static final String WORKFLOW_ID = "HelloActivityWorkflow"; + + @WorkflowInterface + public interface GreetingWorkflow { + + @WorkflowMethod + String getGreeting(String name); + } + + @WorkflowInterface + public interface ChildGreetingWorkflow { + + @WorkflowMethod + String getGreeting(String name); + } + + public static class ChildGreetingWorkflowImpl implements ChildGreetingWorkflow { + + @Override + public String getGreeting(final String name) { + + Workflow.sleep(Duration.ofSeconds(30)); + + return null; + } + } + + @ActivityInterface + public interface GreetingActivities { + + @ActivityMethod(name = "greet") + String composeGreeting(String greeting, String name); + } + + public static class GreetingWorkflowImpl implements GreetingWorkflow { + + private final GreetingActivities activities = + Workflow.newActivityStub( + GreetingActivities.class, + ActivityOptions.newBuilder() + .setCancellationType(ActivityCancellationType.WAIT_CANCELLATION_COMPLETED) + .setStartToCloseTimeout(Duration.ofSeconds(20)) + .setHeartbeatTimeout(Duration.ofSeconds(3)) + .setRetryOptions( + RetryOptions.newBuilder().setInitialInterval(Duration.ofSeconds(10)).build()) + .build()); + + @Override + public String getGreeting(String name) { + String hello = null; + + try { + + final List> promises = new ArrayList<>(); + + ChildWorkflowStub child = + Workflow.newUntypedChildWorkflowStub( + ChildGreetingWorkflow.class.getSimpleName(), + ChildWorkflowOptions.newBuilder() + // + // .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) + // + // .setCancellationType(ChildWorkflowCancellationType.WAIT_CANCELLATION_REQUESTED) + .setWorkflowId("Child_of_" + WORKFLOW_ID) + .build()); + promises.add(child.executeAsync(String.class, "Hello", name)); + + // Wait for the child workflow to start before returning the result + // Promise childExecution = child.getExecution(); + // WorkflowExecution childWorkflowExecution = childExecution.get(); + + promises.add(Async.function(activities::composeGreeting, "Hello", name)); + + for (int i = promises.size() - 1; i >= 0; i--) { + + try { + promises.get(i).get(); + } catch (Exception e) { + System.out.println("In for promise >>>>>>> " + e); + } + } + + } catch (Exception e) { + System.out.println("Something cancelled " + e); + // throw e; + } + + return hello; + } + } + + static class GreetingActivitiesImpl implements GreetingActivities { + private static final Logger log = LoggerFactory.getLogger(GreetingActivitiesImpl.class); + + @Override + public String composeGreeting(String greeting, String name) { + log.info("Composing greeting..."); + + // int i = 0; + + try { + + for (int i = 0; i < 15; i++) { + + System.out.println(">>>>>>> heartbeat " + i); + try { + Activity.getExecutionContext().heartbeat("" + i); + } catch (Exception e) { + System.out.println(">>>>>>> heartbeat " + e); + + // return greeting + " " + name + "!"; + throw e; + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } catch (Exception e) { + System.out.println(">>>>>>> " + e); + + // return greeting + " " + name + "!"; + throw e; + } + return greeting + " " + name + "!"; + } + } + + /** + * With our Workflow and Activities defined, we can now start execution. The main method starts + * the worker and then the workflow. + */ + public static void main(String[] args) { + + // Get a Workflow service stub. + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + + /* + * Get a Workflow service client which can be used to start, Signal, and Query Workflow Executions. + */ + WorkflowClient client = WorkflowClient.newInstance(service); + + /* + * Define the workflow factory. It is used to create workflow workers for a specific task queue. + */ + WorkerFactory factory = WorkerFactory.newInstance(client); + + /* + * Define the workflow worker. Workflow workers listen to a defined task queue and process + * workflows and activities. + */ + Worker worker = factory.newWorker(TASK_QUEUE); + + /* + * Register our workflow implementation with the worker. + * Workflow implementations must be known to the worker at runtime in + * order to dispatch workflow tasks. + */ + worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); + worker.registerWorkflowImplementationTypes(ChildGreetingWorkflowImpl.class); + + /* + * Register our Activity Types with the Worker. Since Activities are stateless and thread-safe, + * the Activity Type is a shared instance. + */ + worker.registerActivitiesImplementations(new GreetingActivitiesImpl()); + + /* + * Start all the workers registered for a specific task queue. + * The started workers then start polling for workflows and activities. + */ + factory.start(); + + // Create the workflow client stub. It is used to start our workflow execution. + GreetingWorkflow workflow = + client.newWorkflowStub( + GreetingWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(WORKFLOW_ID) + .setTaskQueue(TASK_QUEUE) + .build()); + + WorkflowClient.start(workflow::getGreeting, "World"); + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + System.out.println("About to cancel.. "); + client.newUntypedWorkflowStub(WORKFLOW_ID).cancel(); + System.out.println("cancellation request sent .. "); + + // System.exit(0); + } +} diff --git a/core/src/main/java/io/temporal/samples/hello/HelloSignal.java b/core/src/main/java/io/temporal/samples/hello/HelloSignal.java index a302816d..8809ec7e 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloSignal.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloSignal.java @@ -19,16 +19,16 @@ package io.temporal.samples.hello; +import io.temporal.activity.LocalActivityOptions; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowOptions; import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.worker.Worker; import io.temporal.worker.WorkerFactory; -import io.temporal.workflow.SignalMethod; -import io.temporal.workflow.Workflow; -import io.temporal.workflow.WorkflowInterface; -import io.temporal.workflow.WorkflowMethod; +import io.temporal.workflow.*; +import java.time.Duration; import java.util.ArrayList; +import java.util.Date; import java.util.List; /** @@ -67,6 +67,9 @@ public interface GreetingWorkflow { @SignalMethod void waitForName(String name); + @QueryMethod + String query(); + // Define the workflow exit signal method. This method is executed when the workflow receives a // signal. @SignalMethod @@ -76,6 +79,13 @@ public interface GreetingWorkflow { // Define the workflow implementation which implements the getGreetings workflow method. public static class GreetingWorkflowImpl implements GreetingWorkflow { + private final HelloLocalActivity.GreetingActivities activities = + Workflow.newLocalActivityStub( + HelloLocalActivity.GreetingActivities.class, + LocalActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofMinutes(2)) + .build()); + // messageQueue holds up to 10 messages (received from signals) List messageQueue = new ArrayList<>(10); boolean exit = false; @@ -91,6 +101,8 @@ public List getGreetings() { // no messages in queue and exit signal was sent, return the received messages return receivedMessages; } + activities.composeGreeting("", ""); + String message = messageQueue.remove(0); receivedMessages.add(message); } @@ -98,9 +110,15 @@ public List getGreetings() { @Override public void waitForName(String name) { + messageQueue.add("Hello " + name + "!"); } + @Override + public String query() { + return null; + } + @Override public void exit() { exit = true; @@ -138,6 +156,7 @@ public static void main(String[] args) throws Exception { * order to dispatch workflow tasks. */ worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); + worker.registerActivitiesImplementations(new HelloLocalActivity.GreetingLocalActivityImpl()); /* * Start all the workers registered for a specific task queue. @@ -155,15 +174,6 @@ public static void main(String[] args) throws Exception { // Start workflow asynchronously and call its getGreeting workflow method WorkflowClient.start(workflow::getGreetings); - // After start for getGreeting returns, the workflow is guaranteed to be started. - // So we can send a signal to it using the workflow stub. - // This workflow keeps receiving signals until exit is called - - // When the workflow is started the getGreetings will block for the previously defined - // conditions - // Send the first workflow signal - workflow.waitForName("World"); - /* * Here we create a new workflow stub using the same workflow id. * We do this to demonstrate that to send a signal to an already running workflow @@ -174,20 +184,14 @@ public static void main(String[] args) throws Exception { // Send the second signal to our workflow workflowById.waitForName("Universe"); - // Now let's send our exit signal to the workflow - workflowById.exit(); - - /* - * We now call our getGreetings workflow method synchronously after our workflow has started. - * This reconnects our workflowById workflow stub to the existing workflow and blocks until - * a result is available. Note that this behavior assumes that WorkflowOptions are not configured - * with WorkflowIdReusePolicy.AllowDuplicate. If they were, this call would fail with the - * WorkflowExecutionAlreadyStartedException exception. - */ - List greetings = workflowById.getGreetings(); + try { + System.out.println(new Date() + " init "); - // Print our two greetings which were sent by signals - System.out.println(greetings); - System.exit(0); + String result = workflow.query(); + System.out.println(new Date() + " Result " + result); + } catch (Exception e) { + System.out.println(new Date() + " Error "); + e.printStackTrace(); + } } } diff --git a/core/src/main/java/io/temporal/samples/hello/HelloUpdate.java b/core/src/main/java/io/temporal/samples/hello/HelloUpdate.java index 79f20336..ce823449 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloUpdate.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloUpdate.java @@ -144,7 +144,7 @@ public int addGreeting(String name) { throw ApplicationFailure.newFailure("Cannot greet someone with an empty name", "Failure"); } // Updates can mutate workflow state like variables or call activities - messageQueue.add(activities.composeGreeting("Hello", name)); + // messageQueue.add(activities.composeGreeting("Hello", name)); // Updates can return data back to the client return receivedMessages.size() + messageQueue.size(); } diff --git a/core/src/main/java/io/temporal/samples/listworkflows/Starter.java b/core/src/main/java/io/temporal/samples/listworkflows/Starter.java index d03039a7..3aa8700e 100644 --- a/core/src/main/java/io/temporal/samples/listworkflows/Starter.java +++ b/core/src/main/java/io/temporal/samples/listworkflows/Starter.java @@ -103,6 +103,7 @@ private static ListWorkflowExecutionsResponse getExecutionsResponse(String query ListWorkflowExecutionsRequest listWorkflowExecutionRequest = ListWorkflowExecutionsRequest.newBuilder() .setNamespace(client.getOptions().getNamespace()) + .setPageSize(2000) .setQuery(query) .build(); ListWorkflowExecutionsResponse listWorkflowExecutionsResponse = diff --git a/core/src/main/resources/logback.xml b/core/src/main/resources/logback.xml index 418ee244..d900fbbf 100644 --- a/core/src/main/resources/logback.xml +++ b/core/src/main/resources/logback.xml @@ -27,8 +27,8 @@ %d{HH:mm:ss.SSS} {%X{WorkflowId} %X{ActivityId}} [%thread] %-5level %logger{36} - %msg %n - - + + \ No newline at end of file diff --git a/core/src/test/java/io/temporal/samples/hello/TestFailedWorkflowExceptionTypes.java b/core/src/test/java/io/temporal/samples/hello/TestFailedWorkflowExceptionTypes.java new file mode 100644 index 00000000..569e5442 --- /dev/null +++ b/core/src/test/java/io/temporal/samples/hello/TestFailedWorkflowExceptionTypes.java @@ -0,0 +1,85 @@ +package io.temporal.samples.hello; + +import io.temporal.client.*; +import io.temporal.common.converter.EncodedValues; +import io.temporal.failure.ApplicationFailure; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.worker.WorkflowImplementationOptions; +import io.temporal.workflow.DynamicWorkflow; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +public class TestFailedWorkflowExceptionTypes { + private static final String TASK_QUEUE = "default"; + private static final String COMMON_WORKFLOW_ID = "ThrowExceptionWorkflow"; + private static final String DYNAMIC_WORKFLOW_ID = "ThrowExceptionDynamicWorkflow"; + + @WorkflowInterface + public interface ThrowExceptionWorkflow { + @WorkflowMethod + void execute(); + } + + public static class ThrowExceptionWorkflowImpl implements ThrowExceptionWorkflow { + @Override + public void execute() { + throw new RuntimeException("xxx"); + } + } + + public static class ThrowExceptionDynamicWorkflow implements DynamicWorkflow { + @Override + public Object execute(EncodedValues args) { + throw ApplicationFailure.newFailure("xxx", "", ""); + } + } + + public static void main(String[] args) { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClientOptions clientOptions = + WorkflowClientOptions.newBuilder().setNamespace("default").build(); + WorkflowClient client = WorkflowClient.newInstance(service, clientOptions); + WorkerFactory factory = WorkerFactory.newInstance(client); + Worker worker = factory.newWorker(TASK_QUEUE); + + WorkflowImplementationOptions workflowImplementationOptions = + WorkflowImplementationOptions.newBuilder() + .setFailWorkflowExceptionTypes(RuntimeException.class) + .build(); + + worker.registerWorkflowImplementationTypes( + workflowImplementationOptions, + ThrowExceptionWorkflowImpl.class, + ThrowExceptionDynamicWorkflow.class); + factory.start(); + + // common + try { + WorkflowOptions options = + WorkflowOptions.newBuilder() + .setTaskQueue(TASK_QUEUE) + .setWorkflowId(COMMON_WORKFLOW_ID) + .build(); + + client.newWorkflowStub(ThrowExceptionWorkflow.class, options).execute(); + } catch (WorkflowException e) { + System.out.println("COMMON:" + e.getCause().getMessage()); + } + // dynamic + WorkflowOptions workflowOptions = + WorkflowOptions.newBuilder() + .setWorkflowId(DYNAMIC_WORKFLOW_ID) + .setTaskQueue(TASK_QUEUE) + .build(); + WorkflowStub workflowStub = client.newUntypedWorkflowStub("abc", workflowOptions); + + try { + workflowStub.start(); + workflowStub.getResult(Void.class); + } catch (WorkflowException e) { + System.out.println("DYNAMIC:" + e.getMessage()); + } + } +} From d233ea9ae3582914d27f1de3e5e042de23227eae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:33:51 -0400 Subject: [PATCH 09/18] Bump org.mockito:mockito-core from 5.8.0 to 5.11.0 (#594) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.8.0 to 5.11.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.8.0...v5.11.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle b/core/build.gradle index f9c30129..b8706023 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -37,7 +37,7 @@ dependencies { implementation 'com.codingrodent:jackson-json-crypto:1.1.0' testImplementation "junit:junit:4.13.2" - testImplementation "org.mockito:mockito-core:5.8.0" + testImplementation "org.mockito:mockito-core:5.11.0" testImplementation(platform("org.junit:junit-bom:5.10.2")) testImplementation "org.junit.jupiter:junit-jupiter-api" From b571e5971afe11a717694f48af45634f11dd1b3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:35:09 -0400 Subject: [PATCH 10/18] Bump ch.qos.logback:logback-classic from 1.5.0 to 1.5.3 (#600) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.0 to 1.5.3. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.0...v_1.5.3) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle b/core/build.gradle index b8706023..64d5d363 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -11,7 +11,7 @@ dependencies { implementation "io.micrometer:micrometer-registry-prometheus" - implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.0' + implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.3' implementation group: 'com.jayway.jsonpath', name: 'json-path', version: '2.9.0' implementation(platform("io.opentelemetry:opentelemetry-bom:$otelVersion")) From 25dd0b9b962759b26a8d8db8a97098d4e2b83c26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:35:30 -0400 Subject: [PATCH 11/18] Bump com.fasterxml.jackson:jackson-bom from 2.16.1 to 2.17.0 (#601) Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.16.1 to 2.17.0. - [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.16.1...jackson-bom-2.17.0) --- updated-dependencies: - dependency-name: com.fasterxml.jackson:jackson-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle b/core/build.gradle index 64d5d363..9a584b65 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -5,7 +5,7 @@ dependencies { testImplementation("io.temporal:temporal-testing:$javaSDKVersion") // Needed for SDK related functionality - implementation(platform("com.fasterxml.jackson:jackson-bom:2.16.1")) + implementation(platform("com.fasterxml.jackson:jackson-bom:2.17.0")) implementation "com.fasterxml.jackson.core:jackson-databind" implementation "com.fasterxml.jackson.core:jackson-core" From a370f806f9b12f5b29f0db14ca25b77c65fd8757 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:35:52 -0400 Subject: [PATCH 12/18] Bump com.google.errorprone:error_prone_core from 2.25.0 to 2.26.1 (#602) Bumps [com.google.errorprone:error_prone_core](https://github.com/google/error-prone) from 2.25.0 to 2.26.1. - [Release notes](https://github.com/google/error-prone/releases) - [Commits](https://github.com/google/error-prone/compare/v2.25.0...v2.26.1) --- updated-dependencies: - dependency-name: com.google.errorprone:error_prone_core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- core/build.gradle | 2 +- springboot-basic/build.gradle | 4 ++-- springboot/build.gradle | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 9a584b65..68c005aa 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -46,7 +46,7 @@ dependencies { dependencies { errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') - errorprone('com.google.errorprone:error_prone_core:2.25.0') + errorprone('com.google.errorprone:error_prone_core:2.26.1') } } diff --git a/springboot-basic/build.gradle b/springboot-basic/build.gradle index 6a2c9c95..42b8637a 100644 --- a/springboot-basic/build.gradle +++ b/springboot-basic/build.gradle @@ -10,9 +10,9 @@ dependencies { dependencies { errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') if (JavaVersion.current().isJava11Compatible()) { - errorprone('com.google.errorprone:error_prone_core:2.25.0') + errorprone('com.google.errorprone:error_prone_core:2.26.1') } else { - errorprone('com.google.errorprone:error_prone_core:2.25.0') + errorprone('com.google.errorprone:error_prone_core:2.26.1') } } } diff --git a/springboot/build.gradle b/springboot/build.gradle index d9939248..7ae490ca 100644 --- a/springboot/build.gradle +++ b/springboot/build.gradle @@ -16,7 +16,7 @@ dependencies { testImplementation "org.springframework.boot:spring-boot-starter-test" dependencies { errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') - errorprone('com.google.errorprone:error_prone_core:2.25.0') + errorprone('com.google.errorprone:error_prone_core:2.26.1') } } From 31df9e41c0ce84a5a551c8561b67ce2f1ee9fe64 Mon Sep 17 00:00:00 2001 From: Tihomir Surdilovic Date: Wed, 3 Apr 2024 10:32:34 -0400 Subject: [PATCH 13/18] update sdk version to 1.23.1 (#608) Signed-off-by: Tihomir Surdilovic --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2af4a583..c5fa37af 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ subprojects { ext { otelVersion = '1.30.1' otelVersionAlpha = "${otelVersion}-alpha" - javaSDKVersion = '1.23.0' + javaSDKVersion = '1.23.1' camelVersion = '3.22.1' jarVersion = '1.0.0' } From a87db56b3127fd57e272002399ae0738f14565b6 Mon Sep 17 00:00:00 2001 From: Tihomir Surdilovic Date: Wed, 3 Apr 2024 11:56:07 -0400 Subject: [PATCH 14/18] Add KeyWordList type to typed SA sample (#607) * Add KeyWordList type to typed SA sample Signed-off-by: Tihomir Surdilovic * formatting Signed-off-by: Tihomir Surdilovic --------- Signed-off-by: Tihomir Surdilovic --- .../hello/HelloTypedSearchAttributes.java | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/hello/HelloTypedSearchAttributes.java b/core/src/main/java/io/temporal/samples/hello/HelloTypedSearchAttributes.java index 589778c1..df196d76 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloTypedSearchAttributes.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloTypedSearchAttributes.java @@ -34,10 +34,24 @@ import java.time.Duration; import java.time.OffsetDateTime; import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.List; +import java.util.StringJoiner; /** * Sample Temporal workflow that demonstrates setting up, updating, and retrieving workflow search * attributes using the typed search attributes API. + * + *

NOTE: you may need to add these custom search attributes yourself before running the sample. + * If you are using autosetup image for service, you will need to create the + * "CustomKeywordListField" search attribute with Temporal cli, for example: + * + *

temporal operator search-attribute create -name "CustomKeywordListField" -type "KeywordList" + * + *

If you run your test and don't have some custom SA defined that are used here you would see + * error like: INVALID_ARGUMENT: Namespace default has no mapping defined for search attribute + * CustomBoolField when trying to start the workflow execution. In that case use cli to add the + * needed search attribute with its needed type. */ public class HelloTypedSearchAttributes { @@ -50,6 +64,8 @@ public class HelloTypedSearchAttributes { // Define all our search attributes with appropriate types static final SearchAttributeKey CUSTOM_KEYWORD_SA = SearchAttributeKey.forKeyword("CustomKeywordField"); + static final SearchAttributeKey> CUSTOM_KEYWORD_LIST_SA = + SearchAttributeKey.forKeywordList("CustomKeywordListField"); static final SearchAttributeKey CUSTOM_LONG_SA = SearchAttributeKey.forLong("CustomIntField"); static final SearchAttributeKey CUSTOM_DOUBLE_SA = @@ -95,7 +111,7 @@ public interface GreetingWorkflow { @ActivityInterface public interface GreetingActivities { @ActivityMethod - String composeGreeting(String greeting, String name); + String composeGreeting(String greeting, List salutations, String name); } // Define the workflow implementation which implements our getGreeting workflow method. @@ -124,8 +140,9 @@ public String getGreeting(String name) { io.temporal.common.SearchAttributes searchAttributes = Workflow.getTypedSearchAttributes(); // Get a particular value out of the container using the typed key String greeting = searchAttributes.get(CUSTOM_KEYWORD_SA); + List salutations = searchAttributes.get(CUSTOM_KEYWORD_LIST_SA); // This is a blocking call that returns only after the activity has completed. - return activities.composeGreeting(greeting, name); + return activities.composeGreeting(greeting, salutations, name); } } @@ -135,8 +152,13 @@ public String getGreeting(String name) { */ static class GreetingActivitiesImpl implements GreetingActivities { @Override - public String composeGreeting(String greeting, String name) { - return greeting + " " + name + "!"; + public String composeGreeting(String greeting, List salutations, String name) { + StringJoiner greetingJoiner = new StringJoiner(" "); + greetingJoiner.add(greeting); + greetingJoiner.add(name); + salutations.forEach(s -> greetingJoiner.add(s)); + + return greetingJoiner.toString(); } } @@ -211,6 +233,7 @@ public static void main(String[] args) { private static io.temporal.common.SearchAttributes generateTypedSearchAttributes() { return io.temporal.common.SearchAttributes.newBuilder() .set(CUSTOM_KEYWORD_SA, "keyword") + .set(CUSTOM_KEYWORD_LIST_SA, Arrays.asList("how", "are", "you", "doing?")) .set(CUSTOM_LONG_SA, 1l) .set(CUSTOM_DOUBLE_SA, 0.1) .set(CUSTOM_BOOL_SA, true) From 677f8b0460b18e65115af6bb25a4052e77eab316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Tue, 30 Apr 2024 08:02:46 +0200 Subject: [PATCH 15/18] Example for tasks interaction --- .../asyncchild/ParentWorkflowImpl.java | 47 ++-- .../temporal/samples/hello/HelloActivity.java | 37 ++- .../samples/hello/HelloActivityCancel.java | 238 ------------------ .../temporal/samples/hello/HelloSignal.java | 58 ++--- .../temporal/samples/hello/HelloUpdate.java | 2 +- .../samples/listworkflows/Starter.java | 1 - .../samples/taskinteraction/README.md | 47 ++-- .../samples/taskinteraction/Task.java | 54 ++-- .../samples/taskinteraction/TaskClient.java | 10 +- .../samples/taskinteraction/TaskService.java | 96 ++----- ...vityImpl.java => WorkflowTaskManager.java} | 32 ++- .../WorkflowTaskManagerImpl.java | 104 ++++++++ ...skWorkflow.java => WorkflowWithTasks.java} | 4 +- ...owImpl.java => WorkflowWithTasksImpl.java} | 55 ++-- .../ActivityTask.java} | 7 +- .../activity/ActivityTaskImpl.java | 61 +++++ .../{UpdateTask.java => CompleteTask.java} | 39 +-- .../taskinteraction/client/StartWorkflow.java | 20 +- .../taskinteraction/worker/Worker.java | 12 +- core/src/main/resources/logback.xml | 4 +- .../TestFailedWorkflowExceptionTypes.java | 85 ------- .../taskinteraction/TaskWorkflowImplTest.java | 149 ----------- .../WorkflowWithTasksImplTest.java | 183 ++++++++++++++ 23 files changed, 593 insertions(+), 752 deletions(-) delete mode 100644 core/src/main/java/io/temporal/samples/hello/HelloActivityCancel.java rename core/src/main/java/io/temporal/samples/taskinteraction/{TaskActivityImpl.java => WorkflowTaskManager.java} (64%) create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManagerImpl.java rename core/src/main/java/io/temporal/samples/taskinteraction/{TaskWorkflow.java => WorkflowWithTasks.java} (90%) rename core/src/main/java/io/temporal/samples/taskinteraction/{TaskWorkflowImpl.java => WorkflowWithTasksImpl.java} (52%) rename core/src/main/java/io/temporal/samples/taskinteraction/{TaskActivity.java => activity/ActivityTask.java} (82%) create mode 100644 core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTaskImpl.java rename core/src/main/java/io/temporal/samples/taskinteraction/client/{UpdateTask.java => CompleteTask.java} (50%) delete mode 100644 core/src/test/java/io/temporal/samples/hello/TestFailedWorkflowExceptionTypes.java delete mode 100644 core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java create mode 100644 core/src/test/java/io/temporal/samples/taskinteraction/WorkflowWithTasksImplTest.java diff --git a/core/src/main/java/io/temporal/samples/asyncchild/ParentWorkflowImpl.java b/core/src/main/java/io/temporal/samples/asyncchild/ParentWorkflowImpl.java index 61dd13b3..cabd79f9 100644 --- a/core/src/main/java/io/temporal/samples/asyncchild/ParentWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/asyncchild/ParentWorkflowImpl.java @@ -25,27 +25,40 @@ import io.temporal.workflow.ChildWorkflowOptions; import io.temporal.workflow.Promise; import io.temporal.workflow.Workflow; +import java.util.ArrayList; +import java.util.List; public class ParentWorkflowImpl implements ParentWorkflow { @Override public WorkflowExecution executeParent() { - // We set the parentClosePolicy to "Abandon" - // This will allow child workflow to continue execution after parent completes - ChildWorkflowOptions childWorkflowOptions = - ChildWorkflowOptions.newBuilder() - .setWorkflowId("childWorkflow") - .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) - .build(); - - // Get the child workflow stub - ChildWorkflow child = Workflow.newChildWorkflowStub(ChildWorkflow.class, childWorkflowOptions); - // Start the child workflow async - Async.function(child::executeChild); - // Get the child workflow execution promise - Promise childExecution = Workflow.getWorkflowExecution(child); - // Call .get on the promise. This will block until the child workflow starts execution (or start - // fails) - return childExecution.get(); + final List> promises = new ArrayList<>(); + + for (int i = 0; i < 1000; i++) { + // We set the parentClosePolicy to "Abandon" + // This will allow child workflow to continue execution after parent completes + ChildWorkflowOptions childWorkflowOptions = + ChildWorkflowOptions.newBuilder() + .setWorkflowId("childWorkflow" + Math.random()) + .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) + .build(); + + // Get the child workflow stub + ChildWorkflow child = + Workflow.newChildWorkflowStub(ChildWorkflow.class, childWorkflowOptions); + // Start the child workflow async + Async.function(child::executeChild); + // Get the child workflow execution promise + Promise childExecution = Workflow.getWorkflowExecution(child); + // Call .get on the promise. This will block until the child workflow starts execution (or + // start + // fails) + + promises.add(childExecution); + } + + Promise.allOf(promises).get(); + + return promises.get(0).get(); } } diff --git a/core/src/main/java/io/temporal/samples/hello/HelloActivity.java b/core/src/main/java/io/temporal/samples/hello/HelloActivity.java index 8dfb0941..9e46dfe6 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloActivity.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloActivity.java @@ -23,6 +23,7 @@ import io.temporal.activity.ActivityMethod; import io.temporal.activity.ActivityOptions; import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.worker.Worker; import io.temporal.worker.WorkerFactory; @@ -78,7 +79,7 @@ public interface GreetingActivities { // Define your activity method which can be called during workflow execution @ActivityMethod(name = "greet") - int composeGreeting(String greeting, String name); + String composeGreeting(String greeting, String name); } // Define the workflow implementation which implements our getGreeting workflow method. @@ -102,12 +103,7 @@ public static class GreetingWorkflowImpl implements GreetingWorkflow { @Override public String getGreeting(String name) { // This is a blocking call that returns only after the activity has completed. - - activities.composeGreeting("Hello", name); - - Workflow.sleep(Duration.ofSeconds(20)); - - return "hello"; + return activities.composeGreeting("Hello", name); } } @@ -116,10 +112,9 @@ static class GreetingActivitiesImpl implements GreetingActivities { private static final Logger log = LoggerFactory.getLogger(GreetingActivitiesImpl.class); @Override - public int composeGreeting(String greeting, String name) { + public String composeGreeting(String greeting, String name) { log.info("Composing greeting..."); - - return 1; + return greeting + " " + name + "!"; } } @@ -166,5 +161,27 @@ public static void main(String[] args) { * The started workers then start polling for workflows and activities. */ factory.start(); + + // Create the workflow client stub. It is used to start our workflow execution. + GreetingWorkflow workflow = + client.newWorkflowStub( + GreetingWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(WORKFLOW_ID) + .setTaskQueue(TASK_QUEUE) + .build()); + + /* + * Execute our workflow and wait for it to complete. The call to our getGreeting method is + * synchronous. + * + * See {@link io.temporal.samples.hello.HelloSignal} for an example of starting workflow + * without waiting synchronously for its result. + */ + String greeting = workflow.getGreeting("World"); + + // Display workflow execution results + System.out.println(greeting); + System.exit(0); } } diff --git a/core/src/main/java/io/temporal/samples/hello/HelloActivityCancel.java b/core/src/main/java/io/temporal/samples/hello/HelloActivityCancel.java deleted file mode 100644 index c478e1f1..00000000 --- a/core/src/main/java/io/temporal/samples/hello/HelloActivityCancel.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved - * - * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Modifications copyright (C) 2017 Uber Technologies, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not - * use this file except in compliance with the License. A copy of the License is - * located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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.temporal.samples.hello; - -import io.temporal.activity.*; -import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowOptions; -import io.temporal.common.RetryOptions; -import io.temporal.serviceclient.WorkflowServiceStubs; -import io.temporal.worker.Worker; -import io.temporal.worker.WorkerFactory; -import io.temporal.workflow.*; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class HelloActivityCancel { - - static final String TASK_QUEUE = "HelloActivityTaskQueue"; - - static final String WORKFLOW_ID = "HelloActivityWorkflow"; - - @WorkflowInterface - public interface GreetingWorkflow { - - @WorkflowMethod - String getGreeting(String name); - } - - @WorkflowInterface - public interface ChildGreetingWorkflow { - - @WorkflowMethod - String getGreeting(String name); - } - - public static class ChildGreetingWorkflowImpl implements ChildGreetingWorkflow { - - @Override - public String getGreeting(final String name) { - - Workflow.sleep(Duration.ofSeconds(30)); - - return null; - } - } - - @ActivityInterface - public interface GreetingActivities { - - @ActivityMethod(name = "greet") - String composeGreeting(String greeting, String name); - } - - public static class GreetingWorkflowImpl implements GreetingWorkflow { - - private final GreetingActivities activities = - Workflow.newActivityStub( - GreetingActivities.class, - ActivityOptions.newBuilder() - .setCancellationType(ActivityCancellationType.WAIT_CANCELLATION_COMPLETED) - .setStartToCloseTimeout(Duration.ofSeconds(20)) - .setHeartbeatTimeout(Duration.ofSeconds(3)) - .setRetryOptions( - RetryOptions.newBuilder().setInitialInterval(Duration.ofSeconds(10)).build()) - .build()); - - @Override - public String getGreeting(String name) { - String hello = null; - - try { - - final List> promises = new ArrayList<>(); - - ChildWorkflowStub child = - Workflow.newUntypedChildWorkflowStub( - ChildGreetingWorkflow.class.getSimpleName(), - ChildWorkflowOptions.newBuilder() - // - // .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) - // - // .setCancellationType(ChildWorkflowCancellationType.WAIT_CANCELLATION_REQUESTED) - .setWorkflowId("Child_of_" + WORKFLOW_ID) - .build()); - promises.add(child.executeAsync(String.class, "Hello", name)); - - // Wait for the child workflow to start before returning the result - // Promise childExecution = child.getExecution(); - // WorkflowExecution childWorkflowExecution = childExecution.get(); - - promises.add(Async.function(activities::composeGreeting, "Hello", name)); - - for (int i = promises.size() - 1; i >= 0; i--) { - - try { - promises.get(i).get(); - } catch (Exception e) { - System.out.println("In for promise >>>>>>> " + e); - } - } - - } catch (Exception e) { - System.out.println("Something cancelled " + e); - // throw e; - } - - return hello; - } - } - - static class GreetingActivitiesImpl implements GreetingActivities { - private static final Logger log = LoggerFactory.getLogger(GreetingActivitiesImpl.class); - - @Override - public String composeGreeting(String greeting, String name) { - log.info("Composing greeting..."); - - // int i = 0; - - try { - - for (int i = 0; i < 15; i++) { - - System.out.println(">>>>>>> heartbeat " + i); - try { - Activity.getExecutionContext().heartbeat("" + i); - } catch (Exception e) { - System.out.println(">>>>>>> heartbeat " + e); - - // return greeting + " " + name + "!"; - throw e; - } - - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } catch (Exception e) { - System.out.println(">>>>>>> " + e); - - // return greeting + " " + name + "!"; - throw e; - } - return greeting + " " + name + "!"; - } - } - - /** - * With our Workflow and Activities defined, we can now start execution. The main method starts - * the worker and then the workflow. - */ - public static void main(String[] args) { - - // Get a Workflow service stub. - WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); - - /* - * Get a Workflow service client which can be used to start, Signal, and Query Workflow Executions. - */ - WorkflowClient client = WorkflowClient.newInstance(service); - - /* - * Define the workflow factory. It is used to create workflow workers for a specific task queue. - */ - WorkerFactory factory = WorkerFactory.newInstance(client); - - /* - * Define the workflow worker. Workflow workers listen to a defined task queue and process - * workflows and activities. - */ - Worker worker = factory.newWorker(TASK_QUEUE); - - /* - * Register our workflow implementation with the worker. - * Workflow implementations must be known to the worker at runtime in - * order to dispatch workflow tasks. - */ - worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); - worker.registerWorkflowImplementationTypes(ChildGreetingWorkflowImpl.class); - - /* - * Register our Activity Types with the Worker. Since Activities are stateless and thread-safe, - * the Activity Type is a shared instance. - */ - worker.registerActivitiesImplementations(new GreetingActivitiesImpl()); - - /* - * Start all the workers registered for a specific task queue. - * The started workers then start polling for workflows and activities. - */ - factory.start(); - - // Create the workflow client stub. It is used to start our workflow execution. - GreetingWorkflow workflow = - client.newWorkflowStub( - GreetingWorkflow.class, - WorkflowOptions.newBuilder() - .setWorkflowId(WORKFLOW_ID) - .setTaskQueue(TASK_QUEUE) - .build()); - - WorkflowClient.start(workflow::getGreeting, "World"); - - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - System.out.println("About to cancel.. "); - client.newUntypedWorkflowStub(WORKFLOW_ID).cancel(); - System.out.println("cancellation request sent .. "); - - // System.exit(0); - } -} diff --git a/core/src/main/java/io/temporal/samples/hello/HelloSignal.java b/core/src/main/java/io/temporal/samples/hello/HelloSignal.java index 8809ec7e..a302816d 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloSignal.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloSignal.java @@ -19,16 +19,16 @@ package io.temporal.samples.hello; -import io.temporal.activity.LocalActivityOptions; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowOptions; import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.worker.Worker; import io.temporal.worker.WorkerFactory; -import io.temporal.workflow.*; -import java.time.Duration; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.Workflow; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; import java.util.ArrayList; -import java.util.Date; import java.util.List; /** @@ -67,9 +67,6 @@ public interface GreetingWorkflow { @SignalMethod void waitForName(String name); - @QueryMethod - String query(); - // Define the workflow exit signal method. This method is executed when the workflow receives a // signal. @SignalMethod @@ -79,13 +76,6 @@ public interface GreetingWorkflow { // Define the workflow implementation which implements the getGreetings workflow method. public static class GreetingWorkflowImpl implements GreetingWorkflow { - private final HelloLocalActivity.GreetingActivities activities = - Workflow.newLocalActivityStub( - HelloLocalActivity.GreetingActivities.class, - LocalActivityOptions.newBuilder() - .setStartToCloseTimeout(Duration.ofMinutes(2)) - .build()); - // messageQueue holds up to 10 messages (received from signals) List messageQueue = new ArrayList<>(10); boolean exit = false; @@ -101,8 +91,6 @@ public List getGreetings() { // no messages in queue and exit signal was sent, return the received messages return receivedMessages; } - activities.composeGreeting("", ""); - String message = messageQueue.remove(0); receivedMessages.add(message); } @@ -110,15 +98,9 @@ public List getGreetings() { @Override public void waitForName(String name) { - messageQueue.add("Hello " + name + "!"); } - @Override - public String query() { - return null; - } - @Override public void exit() { exit = true; @@ -156,7 +138,6 @@ public static void main(String[] args) throws Exception { * order to dispatch workflow tasks. */ worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); - worker.registerActivitiesImplementations(new HelloLocalActivity.GreetingLocalActivityImpl()); /* * Start all the workers registered for a specific task queue. @@ -174,6 +155,15 @@ public static void main(String[] args) throws Exception { // Start workflow asynchronously and call its getGreeting workflow method WorkflowClient.start(workflow::getGreetings); + // After start for getGreeting returns, the workflow is guaranteed to be started. + // So we can send a signal to it using the workflow stub. + // This workflow keeps receiving signals until exit is called + + // When the workflow is started the getGreetings will block for the previously defined + // conditions + // Send the first workflow signal + workflow.waitForName("World"); + /* * Here we create a new workflow stub using the same workflow id. * We do this to demonstrate that to send a signal to an already running workflow @@ -184,14 +174,20 @@ public static void main(String[] args) throws Exception { // Send the second signal to our workflow workflowById.waitForName("Universe"); - try { - System.out.println(new Date() + " init "); + // Now let's send our exit signal to the workflow + workflowById.exit(); - String result = workflow.query(); - System.out.println(new Date() + " Result " + result); - } catch (Exception e) { - System.out.println(new Date() + " Error "); - e.printStackTrace(); - } + /* + * We now call our getGreetings workflow method synchronously after our workflow has started. + * This reconnects our workflowById workflow stub to the existing workflow and blocks until + * a result is available. Note that this behavior assumes that WorkflowOptions are not configured + * with WorkflowIdReusePolicy.AllowDuplicate. If they were, this call would fail with the + * WorkflowExecutionAlreadyStartedException exception. + */ + List greetings = workflowById.getGreetings(); + + // Print our two greetings which were sent by signals + System.out.println(greetings); + System.exit(0); } } diff --git a/core/src/main/java/io/temporal/samples/hello/HelloUpdate.java b/core/src/main/java/io/temporal/samples/hello/HelloUpdate.java index ce823449..79f20336 100644 --- a/core/src/main/java/io/temporal/samples/hello/HelloUpdate.java +++ b/core/src/main/java/io/temporal/samples/hello/HelloUpdate.java @@ -144,7 +144,7 @@ public int addGreeting(String name) { throw ApplicationFailure.newFailure("Cannot greet someone with an empty name", "Failure"); } // Updates can mutate workflow state like variables or call activities - // messageQueue.add(activities.composeGreeting("Hello", name)); + messageQueue.add(activities.composeGreeting("Hello", name)); // Updates can return data back to the client return receivedMessages.size() + messageQueue.size(); } diff --git a/core/src/main/java/io/temporal/samples/listworkflows/Starter.java b/core/src/main/java/io/temporal/samples/listworkflows/Starter.java index 3aa8700e..d03039a7 100644 --- a/core/src/main/java/io/temporal/samples/listworkflows/Starter.java +++ b/core/src/main/java/io/temporal/samples/listworkflows/Starter.java @@ -103,7 +103,6 @@ private static ListWorkflowExecutionsResponse getExecutionsResponse(String query ListWorkflowExecutionsRequest listWorkflowExecutionRequest = ListWorkflowExecutionsRequest.newBuilder() .setNamespace(client.getOptions().getNamespace()) - .setPageSize(2000) .setQuery(query) .build(); ListWorkflowExecutionsResponse listWorkflowExecutionsResponse = diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/README.md b/core/src/main/java/io/temporal/samples/taskinteraction/README.md index 365b6c6b..065f330b 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/README.md +++ b/core/src/main/java/io/temporal/samples/taskinteraction/README.md @@ -1,38 +1,47 @@ # Demo tasks interaction -This example demonstrate a generic implementation for tasks interaction in Temporal. +This example demonstrates a generic implementation for "User Tasks" interaction with Temporal, +which can be easily implemented as follows: +- The main workflow [WorkflowWithTasks](./WorkflowWithTasks.java) have an activity (or local activity) that send the request to an external service. +The _external service_, for this example, is another workflow ([WorkflowTaskManager](WorkflowTaskManager.java)), +that takes care of the task life-cicle. +- The main workflow waits, with `Workflow.await`, to receive a Signal. +- The _external service_ signal back the main +workflow to unblock it. -The basic implementation consist on three parts: -- One activity (or local activity) that send the request (create a task). -- Block the workflow execution `Workflow.await` awaiting a Signal. -- The workflow will eventually receive a signal that unblocks it. +The two first steps mentioned above are encapsulated in the class [TaskService.java](./TaskService.java), to make it easily reusable. -Additionally, the example allows to track task state (PENDING, STARTED, COMPLETED...). +## Run the sample -> If the client can not send a Signal to the workflow execution, steps 2 and 3 can be replaced by an activity -that polls using one of [these three strategies](../polling). +- Schedule the main workflow execution ([WorkflowWithTasks](./WorkflowWithTasks.java)), the one that contains the _User Tasks_ -## Run the sample +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.StartWorkflow +``` -- Start the worker +- Open other terminal and start the Worker ```bash ./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.worker.Worker ``` -- Schedule workflow execution +You will notice, from the worker logs, that it start the main workflow and execute two activities, the +two activities register two tasks to the external service ([WorkflowTaskManagerImpl.java](WorkflowTaskManagerImpl.java)) -```bash -./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.StartWorkflow +``` +07:19:39.528 {WorkflowWithTasks1714454371179 } [workflow[WorkflowWithTasks1714454371179]-1] INFO i.t.s.taskinteraction.TaskService - Before creating task : Task{token='WorkflowWithTasks1714454371179_1', title=TaskTitle{value='TODO 1'}} +07:19:39.563 {WorkflowWithTasks1714454371179 } [workflow[WorkflowWithTasks1714454371179]-2] INFO i.t.s.taskinteraction.TaskService - Before creating task : Task{token='WorkflowWithTasks1714454371179_2', title=TaskTitle{value='TODO 2'}} +07:19:39.683 {WorkflowWithTasks1714454371179 } [workflow[WorkflowWithTasks1714454371179]-1] INFO i.t.s.taskinteraction.TaskService - Task created: Task{token='WorkflowWithTasks1714454371179_1', title=TaskTitle{value='TODO 1'}} +07:19:39.684 {WorkflowWithTasks1714454371179 } [workflow[WorkflowWithTasks1714454371179]-2] INFO i.t.s.taskinteraction.TaskService - Task created: Task{token='WorkflowWithTasks1714454371179_2', title=TaskTit ``` -- Update task +- Now, we can start completing the tasks using the helper class [CompleteTask.java](./client/CompleteTask.java) -Update one of the open task to the next state (PENDING -> STARTED -> COMPLETED) ```bash -./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.UpdateTask +./gradlew -q execute -PmainClass=io.temporal.samples.taskinteraction.client.CompleteTask ``` +You can see from the implementation that [WorkflowWithTasksImpl](./WorkflowWithTasksImpl.java) has three task. +- two in parallel using `Async.procedure`, +- one blocking task at the end. -The workflow has three task, each task has three different states and is created in PENDING state. -You will have to run this class six times to move each task from PENDING to STARTED and to COMPLETED. -Once the last task is completed the workflow completes. +This class needs to be run three times. After the three task are completed the main workflow completes. diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java index e7f396a3..562d5cd1 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java @@ -19,78 +19,52 @@ package io.temporal.samples.taskinteraction; -import com.fasterxml.jackson.annotation.JsonIgnore; - public class Task { private String token; - private TaskData data; - private State state; + private TaskTitle title; public Task() {} - public Task(String token) { + public Task(String token, TaskTitle title) { this.token = token; - this.state = State.pending; + this.title = title; } public String getToken() { return token; } - public void setData(TaskData data) { - this.data = data; - } - - public TaskData getData() { - return data; - } - - public void setState(State state) { - this.state = state; - } - - public State getState() { - return state; - } - - @JsonIgnore - public boolean isCompleted() { - return State.completed == this.state; + public TaskTitle getTitle() { + return title; } @Override public String toString() { - return "Task{" + "token='" + token + '\'' + ", data=" + data + ", state=" + state + '}'; + return "Task{" + "token='" + token + '\'' + ", title=" + title + '}'; } - public enum State { - pending, - started, - completed - } - - public static class TaskData { + public static class TaskTitle { private String value; - public TaskData() {} + public TaskTitle() {} - public TaskData(final String value) { + public TaskTitle(final String value) { this.value = value; } - public void setValue(final String value) { - this.value = value; - } - public String getValue() { return value; } + public void setValue(final String value) { + this.value = value; + } + @Override public String toString() { - return "TaskData{" + "value='" + value + '\'' + '}'; + return "TaskTitle{" + "value='" + value + '\'' + '}'; } } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java index 9eb251a4..86d6d6c7 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskClient.java @@ -19,16 +19,12 @@ package io.temporal.samples.taskinteraction; -import io.temporal.workflow.QueryMethod; import io.temporal.workflow.SignalMethod; -import java.util.List; +import io.temporal.workflow.WorkflowInterface; -/** Interface used to dynamically register signal and query handlers from the interceptor. */ +@WorkflowInterface public interface TaskClient { @SignalMethod - void updateTask(TaskService.UpdateTaskRequest task); - - @QueryMethod - List getOpenTasks(); + void completeTaskByToken(String taskToken); } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java index 570a3f79..94bb0ced 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java @@ -19,102 +19,54 @@ package io.temporal.samples.taskinteraction; -import com.fasterxml.jackson.annotation.JsonIgnore; +import io.temporal.activity.ActivityOptions; +import io.temporal.samples.taskinteraction.activity.ActivityTask; import io.temporal.workflow.CompletablePromise; import io.temporal.workflow.Workflow; -import java.util.Collections; +import java.time.Duration; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.slf4j.Logger; +/** + * This class responsibility is to register the task in the external system and waits for the + * external system to signal back. + */ public class TaskService { - private final Map tasks = new HashMap<>(); - private final Map> pendingPromises = - Collections.synchronizedMap(new HashMap<>()); + private final ActivityTask activity = + Workflow.newActivityStub( + ActivityTask.class, + ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(5)).build()); - // Exposes signal and query methods that - // allow us to interact with the workflow execution + private final Map> pendingPromises = new HashMap<>(); + private final Logger logger = Workflow.getLogger(TaskService.class); private final TaskClient listener = - new TaskClient() { - - @Override - public void updateTask(UpdateTaskRequest updateTaskRequest) { - - final String token = updateTaskRequest.getToken(); - final Task.TaskData data = updateTaskRequest.getData(); - tasks.get(token).setData(data); - - final Task t = tasks.get(token); + taskToken -> { + logger.info("Completing task with token: " + taskToken); - t.setState(updateTaskRequest.state); - tasks.put(t.getToken(), t); - - logger.info("Task updated: " + t); - - if (updateTaskRequest.state == Task.State.completed) { - final CompletablePromise completablePromise = pendingPromises.get(token); - completablePromise.complete((R) data.getValue()); - } - } - - @Override - public List getOpenTasks() { - return tasks.values().stream().filter(t -> !t.isCompleted()).collect(Collectors.toList()); - } + final CompletablePromise completablePromise = pendingPromises.get(taskToken); + completablePromise.complete(null); }; public TaskService() { Workflow.registerListener(listener); } - private final Logger logger = Workflow.getLogger(TaskService.class); - - public R executeTask(Callback callback, String token) { + public void executeTask(Task task) { - final Task task = new Task(token); logger.info("Before creating task : " + task); - tasks.put(token, task); - callback.execute(); + final String token = task.getToken(); + activity.createTask(task); + logger.info("Task created: " + task); final CompletablePromise promise = Workflow.newPromise(); pendingPromises.put(token, promise); - return promise.get(); - } - - public interface Callback { - T execute(); - } - - public static class UpdateTaskRequest { - - private Task.State state; - private Task.TaskData data; - private String token; - - public UpdateTaskRequest() {} - - public UpdateTaskRequest(Task.State state, Task.TaskData data, String token) { - this.state = state; - this.data = data; - this.token = token; - } - - @JsonIgnore - public boolean isCompleted() { - return this.state == Task.State.completed; - } - - public String getToken() { - return token; - } + // Wait promise to complete or fail + promise.get(); - public Task.TaskData getData() { - return data; - } + logger.info("Task completed: " + task); } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivityImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManager.java similarity index 64% rename from core/src/main/java/io/temporal/samples/taskinteraction/TaskActivityImpl.java rename to core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManager.java index 51ea7133..2caadc1c 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivityImpl.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManager.java @@ -19,17 +19,23 @@ package io.temporal.samples.taskinteraction; -public class TaskActivityImpl implements TaskActivity { - @Override - public String createTask(String task) { - - // Simulating delay in task creation - try { - Thread.sleep(500); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - return "activity created"; - } +import io.temporal.workflow.*; +import java.util.List; + +@WorkflowInterface +public interface WorkflowTaskManager { + + String WORKFLOW_ID = WorkflowTaskManager.class.getSimpleName(); + + @WorkflowMethod + void execute(List inputPendingTask, List inputTaskToComplete); + + @UpdateMethod + void createTask(Task task); + + @UpdateMethod + void completeTaskByToken(String taskToken); + + @QueryMethod + List getPendingTask(); } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManagerImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManagerImpl.java new file mode 100644 index 00000000..ec9ae155 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManagerImpl.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import io.temporal.workflow.Workflow; +import java.util.*; + +public class WorkflowTaskManagerImpl implements WorkflowTaskManager { + + private List pendingTask; + + private List tasksToComplete; + + @Override + public void execute(List inputPendingTask, List inputTaskToComplete) { + initPendingTasks(inputPendingTask); + initTaskToComplete(inputTaskToComplete); + + while (true) { + + Workflow.await( + () -> + // Wait until there are pending task to complete + !tasksToComplete.isEmpty()); + + final String taskToken = tasksToComplete.remove(0); + + // Find the workflow id of the workflow we have to signal back + final String externalWorkflowId = new StringTokenizer(taskToken, "_").nextToken(); + + Workflow.newExternalWorkflowStub(TaskClient.class, externalWorkflowId) + .completeTaskByToken(taskToken); + + final Task task = getPendingTaskWithToken(taskToken).get(); + pendingTask.remove(task); + + if (Workflow.getInfo().isContinueAsNewSuggested()) { + Workflow.newContinueAsNewStub(WorkflowTaskManager.class) + .execute(pendingTask, tasksToComplete); + } + } + } + + @Override + public void createTask(Task task) { + initPendingTasks(new ArrayList<>()); + pendingTask.add(task); + } + + @Override + public void completeTaskByToken(String taskToken) { + + tasksToComplete.add(taskToken); + + Workflow.await( + () -> { + final boolean taskCompleted = + getPendingTask().stream().noneMatch((t) -> Objects.equals(t.getToken(), taskToken)); + + return taskCompleted; + }); + } + + @Override + public List getPendingTask() { + return pendingTask; + } + + private Optional getPendingTaskWithToken(final String taskToken) { + return pendingTask.stream().filter((t) -> t.getToken().equals(taskToken)).findFirst(); + } + + private void initTaskToComplete(final List tasks) { + if (tasksToComplete == null) { + tasksToComplete = new ArrayList<>(); + } + tasksToComplete.addAll(tasks); + } + + private void initPendingTasks(final List tasks) { + + if (pendingTask == null) { + pendingTask = new ArrayList<>(); + } + pendingTask.addAll(tasks); + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflow.java b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowWithTasks.java similarity index 90% rename from core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflow.java rename to core/src/main/java/io/temporal/samples/taskinteraction/WorkflowWithTasks.java index aa8c8fe1..b0b7111f 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflow.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowWithTasks.java @@ -23,7 +23,9 @@ import io.temporal.workflow.WorkflowMethod; @WorkflowInterface -public interface TaskWorkflow { +public interface WorkflowWithTasks { + + String WORKFLOW_ID = WorkflowWithTasks.class.getSimpleName(); @WorkflowMethod void execute(); diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowWithTasksImpl.java similarity index 52% rename from core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/taskinteraction/WorkflowWithTasksImpl.java index 1ed6decb..7abf9630 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowWithTasksImpl.java @@ -19,65 +19,58 @@ package io.temporal.samples.taskinteraction; -import io.temporal.activity.ActivityOptions; import io.temporal.workflow.Async; import io.temporal.workflow.Promise; import io.temporal.workflow.Workflow; -import java.time.Duration; import java.util.Arrays; import org.slf4j.Logger; -public class TaskWorkflowImpl implements TaskWorkflow { +/** Workflow that creates three task and waits for them to complete */ +public class WorkflowWithTasksImpl implements WorkflowWithTasks { - private final Logger logger = Workflow.getLogger(TaskWorkflowImpl.class); + private final Logger logger = Workflow.getLogger(WorkflowWithTasksImpl.class); - private final TaskService taskService = new TaskService<>(); - - private final TaskActivity activity = - Workflow.newActivityStub( - TaskActivity.class, - ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(5)).build()); + private final TaskService taskService = new TaskService<>(); @Override public void execute() { - final TaskToken taskToken = new TaskToken(); // Schedule two "tasks" in parallel. The last parameter is the token the client needs - // to change the task state, and ultimately to complete the task + // to unblock/complete the task. This token contains the workflowId that the + // client can use to create the workflow stub. + final TaskToken taskToken = new TaskToken(); + logger.info("About to create async tasks"); - final Promise task1 = - Async.function( - () -> - taskService.executeTask(() -> activity.createTask("TODO 1"), taskToken.getNext())); + final Promise task1 = + Async.procedure( + () -> { + final Task task = new Task(taskToken.getNext(), new Task.TaskTitle("TODO 1")); + taskService.executeTask(task); + }); - final Promise task2 = - Async.function( - () -> - taskService.executeTask(() -> activity.createTask("TODO 2"), taskToken.getNext())); + final Promise task2 = + Async.procedure( + () -> { + final Task task = new Task(taskToken.getNext(), new Task.TaskTitle("TODO 2")); + taskService.executeTask(task); + }); - logger.info("Awaiting for two tasks to get completed"); + logger.info("Awaiting for the two tasks to complete"); // Block execution until both tasks complete Promise.allOf(Arrays.asList(task1, task2)).get(); - logger.info("Two tasks completed"); + logger.info("Tasks completed"); - logger.info("About to create one blocking task"); // Blocking invocation - taskService.executeTask(() -> activity.createTask("TODO 3"), taskToken.getNext()); - logger.info("Task completed"); + taskService.executeTask(new Task(taskToken.getNext(), new Task.TaskTitle("TODO 3"))); logger.info("Completing workflow"); } private static class TaskToken { - private int taskToken = 1; public String getNext() { - return Workflow.getInfo().getWorkflowId() - + "-" - + Workflow.currentTimeMillis() - + "-" - + taskToken++; + return Workflow.getInfo().getWorkflowId() + "_" + taskToken++; } } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivity.java b/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTask.java similarity index 82% rename from core/src/main/java/io/temporal/samples/taskinteraction/TaskActivity.java rename to core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTask.java index a9e12619..5d90a880 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskActivity.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTask.java @@ -17,12 +17,13 @@ * permissions and limitations under the License. */ -package io.temporal.samples.taskinteraction; +package io.temporal.samples.taskinteraction.activity; import io.temporal.activity.ActivityInterface; +import io.temporal.samples.taskinteraction.Task; @ActivityInterface -public interface TaskActivity { +public interface ActivityTask { - String createTask(String task); + void createTask(Task task); } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTaskImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTaskImpl.java new file mode 100644 index 00000000..9e634b35 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTaskImpl.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction.activity; + +import io.temporal.activity.Activity; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowExecutionAlreadyStarted; +import io.temporal.client.WorkflowOptions; +import io.temporal.samples.taskinteraction.Task; +import io.temporal.samples.taskinteraction.WorkflowTaskManager; +import java.util.ArrayList; + +public class ActivityTaskImpl implements ActivityTask { + + private final WorkflowClient workflowClient; + + public ActivityTaskImpl(WorkflowClient workflowClient) { + this.workflowClient = workflowClient; + } + + @Override + public void createTask(Task task) { + + final String taskQueue = Activity.getExecutionContext().getInfo().getActivityTaskQueue(); + + final WorkflowOptions workflowOptions = + WorkflowOptions.newBuilder() + .setWorkflowId(WorkflowTaskManager.WORKFLOW_ID) + .setTaskQueue(taskQueue) + .build(); + + final WorkflowTaskManager taskManager = + workflowClient.newWorkflowStub(WorkflowTaskManager.class, workflowOptions); + try { + WorkflowClient.start(taskManager::execute, new ArrayList<>(), new ArrayList<>()); + } catch (WorkflowExecutionAlreadyStarted e) { + // expected exception if workflow was started by a previous activity execution. + // This will be handled differently once updateWithStart is implemented + } + + // register the "task" to the external workflow that manages task lifecycle + taskManager.createTask(task); + } +} diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java b/core/src/main/java/io/temporal/samples/taskinteraction/client/CompleteTask.java similarity index 50% rename from core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java rename to core/src/main/java/io/temporal/samples/taskinteraction/client/CompleteTask.java index 5878b121..8cf0d68a 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/client/UpdateTask.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/client/CompleteTask.java @@ -19,37 +19,42 @@ package io.temporal.samples.taskinteraction.client; -import static io.temporal.samples.taskinteraction.client.StartWorkflow.WORKFLOW_ID; - import io.temporal.client.WorkflowClient; import io.temporal.samples.taskinteraction.Task; -import io.temporal.samples.taskinteraction.TaskClient; -import io.temporal.samples.taskinteraction.TaskService; +import io.temporal.samples.taskinteraction.WorkflowTaskManager; import io.temporal.serviceclient.WorkflowServiceStubs; -import java.util.Arrays; import java.util.List; -public class UpdateTask { +/** + * This class helps to complete tasks in the external workflow. Queries for pending task and + * complete one of them + */ +public class CompleteTask { - public static void main(String[] args) { + public static void main(String[] args) throws InterruptedException { final WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); final WorkflowClient client = WorkflowClient.newInstance(service); - final TaskClient taskClient = client.newWorkflowStub(TaskClient.class, WORKFLOW_ID); + // WorkflowTaskManager keeps and manage workflow task lifecycle + final WorkflowTaskManager workflowTaskManager = + client.newWorkflowStub(WorkflowTaskManager.class, WorkflowTaskManager.WORKFLOW_ID); - final List openTasks = taskClient.getOpenTasks(); + Thread.sleep(200); + final List pendingTask = getPendingTask(workflowTaskManager); + System.out.println("Pending task " + pendingTask); - final Task randomOpenTask = openTasks.get(0); - final List states = Arrays.asList(Task.State.values()); + if (!pendingTask.isEmpty()) { - final Task.State nextState = states.get(states.indexOf(randomOpenTask.getState()) + 1); + final Task nextOpenTask = pendingTask.get(0); + System.out.println("Completing task with token " + nextOpenTask); + workflowTaskManager.completeTaskByToken(nextOpenTask.getToken()); + } - System.out.println("\nUpdating task " + randomOpenTask + " to " + nextState); - taskClient.updateTask( - new TaskService.UpdateTaskRequest( - nextState, new Task.TaskData("Updated to " + nextState), randomOpenTask.getToken())); + System.out.println("Pending task " + getPendingTask(workflowTaskManager)); + } - System.exit(0); + private static List getPendingTask(final WorkflowTaskManager workflowTaskManager) { + return workflowTaskManager.getPendingTask(); } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/client/StartWorkflow.java b/core/src/main/java/io/temporal/samples/taskinteraction/client/StartWorkflow.java index 694b0730..78f3f13e 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/client/StartWorkflow.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/client/StartWorkflow.java @@ -23,32 +23,32 @@ import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowOptions; -import io.temporal.samples.taskinteraction.TaskWorkflow; +import io.temporal.samples.taskinteraction.WorkflowWithTasks; import io.temporal.serviceclient.WorkflowServiceStubs; +/** Client that start schedule WorkflowWithTasks. */ public class StartWorkflow { - static final String WORKFLOW_ID = "TaskWorkflow"; - - public static void main(String[] args) { + public static void main(String[] args) throws InterruptedException { final WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); final WorkflowClient client = WorkflowClient.newInstance(service); - final TaskWorkflow workflow = + final WorkflowWithTasks workflow = client.newWorkflowStub( - TaskWorkflow.class, + WorkflowWithTasks.class, WorkflowOptions.newBuilder() - .setWorkflowId(WORKFLOW_ID) + .setWorkflowId(WorkflowWithTasks.WORKFLOW_ID + System.currentTimeMillis()) .setTaskQueue(TASK_QUEUE) .build()); - System.out.println("Starting workflow " + WORKFLOW_ID); + System.out.println("Starting workflow: " + WorkflowWithTasks.WORKFLOW_ID); - // Execute workflow waiting for it to complete. + // Schedule workflow and waiting for it to complete. workflow.execute(); - System.out.println("Workflow completed"); + System.out.println("Workflow completed: " + WorkflowWithTasks.WORKFLOW_ID); + System.exit(0); } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/worker/Worker.java b/core/src/main/java/io/temporal/samples/taskinteraction/worker/Worker.java index f11ed94c..932d037b 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/worker/Worker.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/worker/Worker.java @@ -20,15 +20,16 @@ package io.temporal.samples.taskinteraction.worker; import io.temporal.client.WorkflowClient; -import io.temporal.samples.taskinteraction.TaskActivityImpl; -import io.temporal.samples.taskinteraction.TaskWorkflowImpl; +import io.temporal.samples.taskinteraction.WorkflowTaskManagerImpl; +import io.temporal.samples.taskinteraction.WorkflowWithTasksImpl; +import io.temporal.samples.taskinteraction.activity.ActivityTaskImpl; import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.worker.WorkerFactory; import io.temporal.worker.WorkerFactoryOptions; public class Worker { - public static final String TASK_QUEUE = "TaskWorkflowImplTaskQueue"; + public static final String TASK_QUEUE = "TaskInteractionQueue"; public static void main(String[] args) { @@ -41,8 +42,9 @@ public static void main(String[] args) { final WorkerFactory factory = WorkerFactory.newInstance(client, factoryOptions); io.temporal.worker.Worker worker = factory.newWorker(TASK_QUEUE); - worker.registerWorkflowImplementationTypes(TaskWorkflowImpl.class); - worker.registerActivitiesImplementations(new TaskActivityImpl()); + worker.registerWorkflowImplementationTypes( + WorkflowWithTasksImpl.class, WorkflowTaskManagerImpl.class); + worker.registerActivitiesImplementations(new ActivityTaskImpl(client)); factory.start(); } diff --git a/core/src/main/resources/logback.xml b/core/src/main/resources/logback.xml index d900fbbf..418ee244 100644 --- a/core/src/main/resources/logback.xml +++ b/core/src/main/resources/logback.xml @@ -27,8 +27,8 @@ %d{HH:mm:ss.SSS} {%X{WorkflowId} %X{ActivityId}} [%thread] %-5level %logger{36} - %msg %n - - + + \ No newline at end of file diff --git a/core/src/test/java/io/temporal/samples/hello/TestFailedWorkflowExceptionTypes.java b/core/src/test/java/io/temporal/samples/hello/TestFailedWorkflowExceptionTypes.java deleted file mode 100644 index 569e5442..00000000 --- a/core/src/test/java/io/temporal/samples/hello/TestFailedWorkflowExceptionTypes.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.temporal.samples.hello; - -import io.temporal.client.*; -import io.temporal.common.converter.EncodedValues; -import io.temporal.failure.ApplicationFailure; -import io.temporal.serviceclient.WorkflowServiceStubs; -import io.temporal.worker.Worker; -import io.temporal.worker.WorkerFactory; -import io.temporal.worker.WorkflowImplementationOptions; -import io.temporal.workflow.DynamicWorkflow; -import io.temporal.workflow.WorkflowInterface; -import io.temporal.workflow.WorkflowMethod; - -public class TestFailedWorkflowExceptionTypes { - private static final String TASK_QUEUE = "default"; - private static final String COMMON_WORKFLOW_ID = "ThrowExceptionWorkflow"; - private static final String DYNAMIC_WORKFLOW_ID = "ThrowExceptionDynamicWorkflow"; - - @WorkflowInterface - public interface ThrowExceptionWorkflow { - @WorkflowMethod - void execute(); - } - - public static class ThrowExceptionWorkflowImpl implements ThrowExceptionWorkflow { - @Override - public void execute() { - throw new RuntimeException("xxx"); - } - } - - public static class ThrowExceptionDynamicWorkflow implements DynamicWorkflow { - @Override - public Object execute(EncodedValues args) { - throw ApplicationFailure.newFailure("xxx", "", ""); - } - } - - public static void main(String[] args) { - WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); - WorkflowClientOptions clientOptions = - WorkflowClientOptions.newBuilder().setNamespace("default").build(); - WorkflowClient client = WorkflowClient.newInstance(service, clientOptions); - WorkerFactory factory = WorkerFactory.newInstance(client); - Worker worker = factory.newWorker(TASK_QUEUE); - - WorkflowImplementationOptions workflowImplementationOptions = - WorkflowImplementationOptions.newBuilder() - .setFailWorkflowExceptionTypes(RuntimeException.class) - .build(); - - worker.registerWorkflowImplementationTypes( - workflowImplementationOptions, - ThrowExceptionWorkflowImpl.class, - ThrowExceptionDynamicWorkflow.class); - factory.start(); - - // common - try { - WorkflowOptions options = - WorkflowOptions.newBuilder() - .setTaskQueue(TASK_QUEUE) - .setWorkflowId(COMMON_WORKFLOW_ID) - .build(); - - client.newWorkflowStub(ThrowExceptionWorkflow.class, options).execute(); - } catch (WorkflowException e) { - System.out.println("COMMON:" + e.getCause().getMessage()); - } - // dynamic - WorkflowOptions workflowOptions = - WorkflowOptions.newBuilder() - .setWorkflowId(DYNAMIC_WORKFLOW_ID) - .setTaskQueue(TASK_QUEUE) - .build(); - WorkflowStub workflowStub = client.newUntypedWorkflowStub("abc", workflowOptions); - - try { - workflowStub.start(); - workflowStub.getResult(Void.class); - } catch (WorkflowException e) { - System.out.println("DYNAMIC:" + e.getMessage()); - } - } -} diff --git a/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java b/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java deleted file mode 100644 index 617d3007..00000000 --- a/core/src/test/java/io/temporal/samples/taskinteraction/TaskWorkflowImplTest.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved - * - * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Modifications copyright (C) 2017 Uber Technologies, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not - * use this file except in compliance with the License. A copy of the License is - * located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import io.temporal.api.common.v1.WorkflowExecution; -import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowStub; -import io.temporal.common.interceptors.*; -import io.temporal.testing.TestWorkflowRule; -import io.temporal.worker.WorkerFactoryOptions; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Rule; -import org.junit.Test; - -public class TaskWorkflowImplTest { - - final CompletableFuture[] completedActivities = - new CompletableFuture[] {new CompletableFuture<>()}; - - @Rule - public TestWorkflowRule testWorkflowRule = - TestWorkflowRule.newBuilder() - .setWorkerFactoryOptions( - WorkerFactoryOptions.newBuilder() - .setWorkerInterceptors(new MyWorkerInterceptor(completedActivities)) - .validateAndBuildWithDefaults()) - .setWorkflowTypes(TaskWorkflowImpl.class) - .setDoNotStart(true) - .build(); - - @Test - public void testRunWorkflow() throws InterruptedException, ExecutionException { - - final TaskActivity activities = mock(TaskActivity.class); - when(activities.createTask(any())).thenReturn("done"); - testWorkflowRule.getWorker().registerActivitiesImplementations(activities); - - testWorkflowRule.getTestEnvironment().start(); - - // Get stub to the dynamically registered interface - TaskWorkflow workflow = testWorkflowRule.newWorkflowStub(TaskWorkflow.class); - - // start workflow execution - WorkflowExecution execution = WorkflowClient.start(workflow::execute); - - final TaskClient taskClient = - testWorkflowRule - .getWorkflowClient() - .newWorkflowStub(TaskClient.class, execution.getWorkflowId()); - - // Wait for the activity to get executed two times. Two tasks created - completedActivities[0].get(); - - final List asyncTasksStarted = taskClient.getOpenTasks(); - assertEquals(2, asyncTasksStarted.size()); - // Change tasks state, not completing them yet - changeTaskState(taskClient, asyncTasksStarted, Task.State.started); - - // The two tasks are still open, lets complete them - final List asyncTasksPending = taskClient.getOpenTasks(); - assertEquals(2, asyncTasksPending.size()); - changeTaskState(taskClient, asyncTasksPending, Task.State.completed); - - // Wait for the activity to get executed for the third time. One more task gets created - completedActivities[0].get(); - - final List syncTask = taskClient.getOpenTasks(); - assertEquals(1, syncTask.size()); - changeTaskState(taskClient, syncTask, Task.State.started); - changeTaskState(taskClient, syncTask, Task.State.completed); - - // Workflow completes - final WorkflowStub untyped = - testWorkflowRule.getWorkflowClient().newUntypedWorkflowStub(execution.getWorkflowId()); - untyped.getResult(String.class); - } - - private static void changeTaskState(TaskClient client, List tasks, Task.State state) { - tasks.forEach( - t -> { - client.updateTask( - new TaskService.UpdateTaskRequest( - state, new Task.TaskData("Changing state to: " + state), t.getToken())); - }); - } - - private final AtomicInteger integer = new AtomicInteger(0); - - private class OnActivityCompleteInterceptor extends ActivityInboundCallsInterceptorBase { - private CompletableFuture[] completableFuture; - - public OnActivityCompleteInterceptor( - final ActivityInboundCallsInterceptor next, - final CompletableFuture[] completableFuture) { - super(next); - this.completableFuture = completableFuture; - } - - @Override - public ActivityOutput execute(final ActivityInput input) { - final ActivityOutput execute = super.execute(input); - integer.getAndIncrement(); - if (Arrays.asList(2, 3).contains(integer.get())) { - completableFuture[0].complete(null); - completableFuture[0] = new CompletableFuture<>(); - } - return execute; - } - } - - private class MyWorkerInterceptor extends WorkerInterceptorBase { - - private CompletableFuture[] completableFuture; - - public MyWorkerInterceptor(final CompletableFuture[] completableFuture) { - this.completableFuture = completableFuture; - } - - public ActivityInboundCallsInterceptor interceptActivity(ActivityInboundCallsInterceptor next) { - return new OnActivityCompleteInterceptor(next, this.completableFuture); - } - } -} diff --git a/core/src/test/java/io/temporal/samples/taskinteraction/WorkflowWithTasksImplTest.java b/core/src/test/java/io/temporal/samples/taskinteraction/WorkflowWithTasksImplTest.java new file mode 100644 index 00000000..8bb9aaa7 --- /dev/null +++ b/core/src/test/java/io/temporal/samples/taskinteraction/WorkflowWithTasksImplTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved + * + * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Modifications copyright (C) 2017 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not + * use this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.temporal.samples.taskinteraction; + +import static org.junit.Assert.assertEquals; + +import io.temporal.api.common.v1.WorkflowExecution; +import io.temporal.api.enums.v1.WorkflowExecutionStatus; +import io.temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest; +import io.temporal.api.workflowservice.v1.DescribeWorkflowExecutionResponse; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.common.interceptors.*; +import io.temporal.samples.taskinteraction.activity.ActivityTaskImpl; +import io.temporal.testing.TestWorkflowRule; +import io.temporal.worker.WorkerFactoryOptions; +import io.temporal.workflow.Workflow; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.junit.Rule; +import org.junit.Test; + +public class WorkflowWithTasksImplTest { + + private MyWorkerInterceptor myWorkerInterceptor = new MyWorkerInterceptor(); + + @Rule + public TestWorkflowRule testWorkflowRule = + TestWorkflowRule.newBuilder() + .setWorkerFactoryOptions( + WorkerFactoryOptions.newBuilder() + .setWorkerInterceptors(myWorkerInterceptor) + .validateAndBuildWithDefaults()) + .setDoNotStart(true) + .build(); + + @Test + public void testEnd2End() { + + final WorkflowClient workflowClient = testWorkflowRule.getTestEnvironment().getWorkflowClient(); + testWorkflowRule + .getWorker() + .registerWorkflowImplementationTypes( + WorkflowWithTasksImpl.class, WorkflowTaskManagerImpl.class); + + testWorkflowRule + .getWorker() + .registerActivitiesImplementations(new ActivityTaskImpl(workflowClient)); + + testWorkflowRule.getTestEnvironment().start(); + + WorkflowWithTasks workflow = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub( + WorkflowWithTasks.class, + WorkflowOptions.newBuilder() + .setWorkflowId(WorkflowWithTasks.WORKFLOW_ID) + .setTaskQueue(testWorkflowRule.getTaskQueue()) + .build()); + + WorkflowExecution execution = WorkflowClient.start(workflow::execute); + + // Wait until the first two tasks from WorkflowWithTasks are created in WorkflowTaskManager + myWorkerInterceptor.waitUntilTwoCreateTaskInvocations(); + + WorkflowTaskManager workflowManager = + workflowClient.newWorkflowStub(WorkflowTaskManager.class, WorkflowTaskManager.WORKFLOW_ID); + + final List pendingTask = getPendingTask(workflowManager); + assertEquals(2, pendingTask.size()); + + // Complete the two pending task created in parallel from `WorkflowWithTasks`. + // Send update to the workflow that keeps tasks state, that will signal back + // the `WorkflowWithTasks` execution + workflowManager.completeTaskByToken(pendingTask.get(0).getToken()); + workflowManager.completeTaskByToken(pendingTask.get(1).getToken()); + + // Wait until the last task in WorkflowWithTasks is created in WorkflowTaskManager + myWorkerInterceptor.waitUntilThreeInvocationsOfCreateTask(); + + // Complete the last task in `WorkflowWithTasks` + assertEquals(1, getPendingTask(workflowManager).size()); + workflowManager.completeTaskByToken(getPendingTask(workflowManager).get(0).getToken()); + + // Wait workflow to complete + workflowClient.newUntypedWorkflowStub(execution.getWorkflowId()).getResult(Void.class); + + final DescribeWorkflowExecutionResponse describeWorkflowExecutionResponse = + getDescribeWorkflowExecutionResponse(workflowClient, execution); + assertEquals( + WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED, + describeWorkflowExecutionResponse.getWorkflowExecutionInfo().getStatus()); + } + + private DescribeWorkflowExecutionResponse getDescribeWorkflowExecutionResponse( + final WorkflowClient workflowClient, final WorkflowExecution execution) { + return workflowClient + .getWorkflowServiceStubs() + .blockingStub() + .describeWorkflowExecution( + DescribeWorkflowExecutionRequest.newBuilder() + .setNamespace(testWorkflowRule.getTestEnvironment().getNamespace()) + .setExecution(execution) + .build()); + } + + private static List getPendingTask(final WorkflowTaskManager workflowManager) { + return workflowManager.getPendingTask(); + } + + private class MyWorkerInterceptor extends WorkerInterceptorBase { + + private int createTaskInvocations = 0; + + private CompletableFuture waitUntilTwoInvocationsOfCreateTask; + + private CompletableFuture waitUntilThreeInvocationsOfCreateTask; + + public MyWorkerInterceptor() { + waitUntilTwoInvocationsOfCreateTask = new CompletableFuture<>(); + waitUntilThreeInvocationsOfCreateTask = new CompletableFuture<>(); + } + + public Void waitUntilTwoCreateTaskInvocations() { + return getFromCompletableFuture(waitUntilTwoInvocationsOfCreateTask); + } + + public Void waitUntilThreeInvocationsOfCreateTask() { + return getFromCompletableFuture(waitUntilThreeInvocationsOfCreateTask); + } + + private Void getFromCompletableFuture(final CompletableFuture completableFuture) { + try { + return completableFuture.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public WorkflowInboundCallsInterceptor interceptWorkflow( + final WorkflowInboundCallsInterceptor next) { + return new WorkflowInboundCallsInterceptorBase(next) { + @Override + public UpdateOutput executeUpdate(final UpdateInput input) { + if (input.getUpdateName().equals("createTask") + && Workflow.getInfo() + .getWorkflowType() + .equals(WorkflowTaskManager.class.getSimpleName())) { + createTaskInvocations++; + if (createTaskInvocations == 2) { + waitUntilTwoInvocationsOfCreateTask.complete(null); + } + + if (createTaskInvocations == 3) { + waitUntilThreeInvocationsOfCreateTask.complete(null); + } + } + + return super.executeUpdate(input); + } + }; + } + } +} From c8742b10f019cbd093e8c6873ddd00759ff0b511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Tue, 30 Apr 2024 08:13:19 +0200 Subject: [PATCH 16/18] fix commented code --- build.gradle | 2 +- .../asyncchild/ParentWorkflowImpl.java | 47 +++++++------------ 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index c5fa37af..a31abc5a 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ subprojects { apply plugin: 'com.diffplug.spotless' compileJava { -// options.compilerArgs << "-Werror" + options.compilerArgs << "-Werror" } java { diff --git a/core/src/main/java/io/temporal/samples/asyncchild/ParentWorkflowImpl.java b/core/src/main/java/io/temporal/samples/asyncchild/ParentWorkflowImpl.java index cabd79f9..61dd13b3 100644 --- a/core/src/main/java/io/temporal/samples/asyncchild/ParentWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/asyncchild/ParentWorkflowImpl.java @@ -25,40 +25,27 @@ import io.temporal.workflow.ChildWorkflowOptions; import io.temporal.workflow.Promise; import io.temporal.workflow.Workflow; -import java.util.ArrayList; -import java.util.List; public class ParentWorkflowImpl implements ParentWorkflow { @Override public WorkflowExecution executeParent() { - final List> promises = new ArrayList<>(); - - for (int i = 0; i < 1000; i++) { - // We set the parentClosePolicy to "Abandon" - // This will allow child workflow to continue execution after parent completes - ChildWorkflowOptions childWorkflowOptions = - ChildWorkflowOptions.newBuilder() - .setWorkflowId("childWorkflow" + Math.random()) - .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) - .build(); - - // Get the child workflow stub - ChildWorkflow child = - Workflow.newChildWorkflowStub(ChildWorkflow.class, childWorkflowOptions); - // Start the child workflow async - Async.function(child::executeChild); - // Get the child workflow execution promise - Promise childExecution = Workflow.getWorkflowExecution(child); - // Call .get on the promise. This will block until the child workflow starts execution (or - // start - // fails) - - promises.add(childExecution); - } - - Promise.allOf(promises).get(); - - return promises.get(0).get(); + // We set the parentClosePolicy to "Abandon" + // This will allow child workflow to continue execution after parent completes + ChildWorkflowOptions childWorkflowOptions = + ChildWorkflowOptions.newBuilder() + .setWorkflowId("childWorkflow") + .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON) + .build(); + + // Get the child workflow stub + ChildWorkflow child = Workflow.newChildWorkflowStub(ChildWorkflow.class, childWorkflowOptions); + // Start the child workflow async + Async.function(child::executeChild); + // Get the child workflow execution promise + Promise childExecution = Workflow.getWorkflowExecution(child); + // Call .get on the promise. This will block until the child workflow starts execution (or start + // fails) + return childExecution.get(); } } From 0043cc8f01c993ca9dca58d3f9cd3267cb784810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Tue, 30 Apr 2024 20:01:00 +0200 Subject: [PATCH 17/18] fix readme --- .../src/main/java/io/temporal/samples/taskinteraction/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/README.md b/core/src/main/java/io/temporal/samples/taskinteraction/README.md index 065f330b..522254c9 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/README.md +++ b/core/src/main/java/io/temporal/samples/taskinteraction/README.md @@ -5,7 +5,7 @@ which can be easily implemented as follows: - The main workflow [WorkflowWithTasks](./WorkflowWithTasks.java) have an activity (or local activity) that send the request to an external service. The _external service_, for this example, is another workflow ([WorkflowTaskManager](WorkflowTaskManager.java)), that takes care of the task life-cicle. -- The main workflow waits, with `Workflow.await`, to receive a Signal. +- The main workflow waits to receive a Signal. - The _external service_ signal back the main workflow to unblock it. From b2a4f67a7a636c081179dec9179dd9606d5a417d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Mendoza=20P=C3=A9rez?= Date: Wed, 29 May 2024 10:35:38 +0200 Subject: [PATCH 18/18] code-refactor --- .../samples/taskinteraction/Task.java | 28 +++++ .../samples/taskinteraction/TaskService.java | 49 +++++--- .../taskinteraction/WorkflowTaskManager.java | 2 +- .../WorkflowTaskManagerImpl.java | 107 ++++++++++-------- .../activity/ActivityTaskImpl.java | 8 +- 5 files changed, 126 insertions(+), 68 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java index 562d5cd1..f3ddc222 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/Task.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/Task.java @@ -19,6 +19,8 @@ package io.temporal.samples.taskinteraction; +import java.util.Objects; + public class Task { private String token; @@ -39,6 +41,19 @@ public TaskTitle getTitle() { return title; } + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof Task)) return false; + final Task task = (Task) o; + return Objects.equals(token, task.token) && Objects.equals(title, task.title); + } + + @Override + public int hashCode() { + return Objects.hash(token, title); + } + @Override public String toString() { return "Task{" + "token='" + token + '\'' + ", title=" + title + '}'; @@ -62,6 +77,19 @@ public void setValue(final String value) { this.value = value; } + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof TaskTitle)) return false; + final TaskTitle taskTitle = (TaskTitle) o; + return Objects.equals(value, taskTitle.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + @Override public String toString() { return "TaskTitle{" + "value='" + value + '\'' + '}'; diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java index 94bb0ced..651ec989 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/TaskService.java @@ -39,34 +39,53 @@ public class TaskService { ActivityTask.class, ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(5)).build()); - private final Map> pendingPromises = new HashMap<>(); - private final Logger logger = Workflow.getLogger(TaskService.class); - private final TaskClient listener = - taskToken -> { - logger.info("Completing task with token: " + taskToken); + private TaskManager tasksManager = new TaskManager(); - final CompletablePromise completablePromise = pendingPromises.get(taskToken); - completablePromise.complete(null); - }; + private final Logger logger = Workflow.getLogger(TaskService.class); public TaskService() { - Workflow.registerListener(listener); + + // This listener exposes a signal method that clients use to notify the task has been completed + Workflow.registerListener( + new TaskClient() { + @Override + public void completeTaskByToken(final String taskToken) { + logger.info("Completing task with token: " + taskToken); + tasksManager.completeTask(taskToken); + } + }); } public void executeTask(Task task) { logger.info("Before creating task : " + task); - final String token = task.getToken(); + + // Activity implementation is responsible for registering the task to the external service + // (which is responsible for managing the task life-cycle) activity.createTask(task); logger.info("Task created: " + task); - final CompletablePromise promise = Workflow.newPromise(); - pendingPromises.put(token, promise); - - // Wait promise to complete or fail - promise.get(); + tasksManager.waitForTaskCompletion(task); logger.info("Task completed: " + task); } + + private class TaskManager { + + private final Map> tasks = new HashMap<>(); + + public void waitForTaskCompletion(final Task task) { + final CompletablePromise promise = Workflow.newPromise(); + tasks.put(task.getToken(), promise); + // Wait promise to complete + promise.get(); + } + + public void completeTask(final String taskToken) { + + final CompletablePromise completablePromise = tasks.get(taskToken); + completablePromise.complete(null); + } + } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManager.java b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManager.java index 2caadc1c..1cf2a84c 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManager.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManager.java @@ -28,7 +28,7 @@ public interface WorkflowTaskManager { String WORKFLOW_ID = WorkflowTaskManager.class.getSimpleName(); @WorkflowMethod - void execute(List inputPendingTask, List inputTaskToComplete); + void execute(final WorkflowTaskManagerImpl.PendingTasks pendingTasks); @UpdateMethod void createTask(Task task); diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManagerImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManagerImpl.java index ec9ae155..a0a0a336 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManagerImpl.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/WorkflowTaskManagerImpl.java @@ -20,85 +20,94 @@ package io.temporal.samples.taskinteraction; import io.temporal.workflow.Workflow; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.StringTokenizer; public class WorkflowTaskManagerImpl implements WorkflowTaskManager { - private List pendingTask; - - private List tasksToComplete; + private PendingTasks pendingTasks = new PendingTasks(); @Override - public void execute(List inputPendingTask, List inputTaskToComplete) { - initPendingTasks(inputPendingTask); - initTaskToComplete(inputTaskToComplete); - - while (true) { - - Workflow.await( - () -> - // Wait until there are pending task to complete - !tasksToComplete.isEmpty()); + public void execute(final PendingTasks inputPendingTasks) { - final String taskToken = tasksToComplete.remove(0); + initTaskList(inputPendingTasks); - // Find the workflow id of the workflow we have to signal back - final String externalWorkflowId = new StringTokenizer(taskToken, "_").nextToken(); + Workflow.await(() -> Workflow.getInfo().isContinueAsNewSuggested()); - Workflow.newExternalWorkflowStub(TaskClient.class, externalWorkflowId) - .completeTaskByToken(taskToken); - - final Task task = getPendingTaskWithToken(taskToken).get(); - pendingTask.remove(task); - - if (Workflow.getInfo().isContinueAsNewSuggested()) { - Workflow.newContinueAsNewStub(WorkflowTaskManager.class) - .execute(pendingTask, tasksToComplete); - } - } + Workflow.newContinueAsNewStub(WorkflowTaskManager.class).execute(this.pendingTasks); } @Override public void createTask(Task task) { - initPendingTasks(new ArrayList<>()); - pendingTask.add(task); + initTaskList(new PendingTasks()); + pendingTasks.addTask(task); } @Override public void completeTaskByToken(String taskToken) { - tasksToComplete.add(taskToken); + Task task = this.pendingTasks.filterTaskByToken(taskToken).get(); - Workflow.await( - () -> { - final boolean taskCompleted = - getPendingTask().stream().noneMatch((t) -> Objects.equals(t.getToken(), taskToken)); + final String externalWorkflowId = extractWorkflowIdFromTaskToken(taskToken); - return taskCompleted; - }); + // Signal back to the workflow that started this task to notify that the task was completed + Workflow.newExternalWorkflowStub(TaskClient.class, externalWorkflowId) + .completeTaskByToken(taskToken); + + this.pendingTasks.markTaskAsCompleted(task); } @Override public List getPendingTask() { - return pendingTask; + return pendingTasks.getTasks(); } - private Optional getPendingTaskWithToken(final String taskToken) { - return pendingTask.stream().filter((t) -> t.getToken().equals(taskToken)).findFirst(); - } + private void initTaskList(final PendingTasks pendingTasks) { + this.pendingTasks = this.pendingTasks == null ? new PendingTasks() : this.pendingTasks; - private void initTaskToComplete(final List tasks) { - if (tasksToComplete == null) { - tasksToComplete = new ArrayList<>(); + // Update method addTask can be invoked before the main workflow method. + if (pendingTasks != null) { + this.pendingTasks.addAll(pendingTasks.getTasks()); } - tasksToComplete.addAll(tasks); } - private void initPendingTasks(final List tasks) { + private String extractWorkflowIdFromTaskToken(final String taskToken) { + return new StringTokenizer(taskToken, "_").nextToken(); + } + + public static class PendingTasks { + private final List tasks; + + public PendingTasks() { + this(new ArrayList<>()); + } + + public PendingTasks(final List tasks) { + this.tasks = tasks; + } + + public void addTask(final Task task) { + this.tasks.add(task); + } + + public void addAll(final List tasks) { + this.tasks.addAll(tasks); + } + + public void markTaskAsCompleted(final Task task) { + // For the sake of simplicity, we delete the task if it is marked as completed. + // Nothing stops us from having a field to track the tasks' state + tasks.remove(task); + } + + private Optional filterTaskByToken(final String taskToken) { + return tasks.stream().filter((t) -> t.getToken().equals(taskToken)).findFirst(); + } - if (pendingTask == null) { - pendingTask = new ArrayList<>(); + private List getTasks() { + return tasks; } - pendingTask.addAll(tasks); } } diff --git a/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTaskImpl.java b/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTaskImpl.java index 9e634b35..7c3b3ab4 100644 --- a/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTaskImpl.java +++ b/core/src/main/java/io/temporal/samples/taskinteraction/activity/ActivityTaskImpl.java @@ -25,7 +25,7 @@ import io.temporal.client.WorkflowOptions; import io.temporal.samples.taskinteraction.Task; import io.temporal.samples.taskinteraction.WorkflowTaskManager; -import java.util.ArrayList; +import io.temporal.samples.taskinteraction.WorkflowTaskManagerImpl; public class ActivityTaskImpl implements ActivityTask { @@ -35,11 +35,13 @@ public ActivityTaskImpl(WorkflowClient workflowClient) { this.workflowClient = workflowClient; } + // This activity is responsible for registering the task to the external service @Override public void createTask(Task task) { final String taskQueue = Activity.getExecutionContext().getInfo().getActivityTaskQueue(); + // In this case the service that manages the task life-cycle is another workflow. final WorkflowOptions workflowOptions = WorkflowOptions.newBuilder() .setWorkflowId(WorkflowTaskManager.WORKFLOW_ID) @@ -49,13 +51,13 @@ public void createTask(Task task) { final WorkflowTaskManager taskManager = workflowClient.newWorkflowStub(WorkflowTaskManager.class, workflowOptions); try { - WorkflowClient.start(taskManager::execute, new ArrayList<>(), new ArrayList<>()); + WorkflowClient.start(taskManager::execute, new WorkflowTaskManagerImpl.PendingTasks()); } catch (WorkflowExecutionAlreadyStarted e) { // expected exception if workflow was started by a previous activity execution. // This will be handled differently once updateWithStart is implemented } - // register the "task" to the external workflow that manages task lifecycle + // Register the "task" to the external workflow and return taskManager.createTask(task); } }