diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java index 3d17108e..0efbace2 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java @@ -25,4 +25,28 @@ public interface WorkflowInstance extends WorkflowInstanceData { boolean cancel(); boolean resume(); + + /** + * Returns the workflow output. + * + *

This method may block until the workflow execution has completed. Callers should not invoke + * it from lifecycle callbacks, listener threads, or other execution contexts where blocking is + * not safe. + * + * @return the workflow output + */ + WorkflowModel output(); + + /** + * Returns the workflow output converted to the requested type. + * + *

This method may block until the workflow execution has completed. Callers should not invoke + * it from lifecycle callbacks, listener threads, or other execution contexts where blocking is + * not safe. + * + * @param clazz the target output type + * @param the target output type + * @return the workflow output converted to {@code clazz} + */ + T outputAs(Class clazz); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstanceData.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstanceData.java index 10a2e0b4..f283f148 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstanceData.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstanceData.java @@ -28,9 +28,5 @@ public interface WorkflowInstanceData { WorkflowStatus status(); - WorkflowModel output(); - WorkflowModel context(); - - T outputAs(Class clazz); } diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/WorkflowExecutionListenerOutputTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/WorkflowExecutionListenerOutputTest.java new file mode 100644 index 00000000..792d6d60 --- /dev/null +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/WorkflowExecutionListenerOutputTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.test; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.lifecycle.WorkflowCompletedEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Test; + +/** + * Verifies that {@code event.output()} in {@code onWorkflowCompleted} carries the workflow's final + * output. {@code WorkflowCompletedEvent.output()} is the correct API for accessing output inside + * the hook — it is populated directly from the task result before the event is published. + * + *

{@code instanceData().output()} is intentionally absent from {@link + * io.serverlessworkflow.impl.WorkflowInstanceData}: it is a blocking join on the workflow future + * intended for callers outside the event chain (e.g. after {@code instance.start().join()}). + */ +class WorkflowExecutionListenerOutputTest { + + @Test + void eventOutputIsPopulatedInOnWorkflowCompleted() throws IOException { + AtomicReference capturedOutput = new AtomicReference<>(); + + WorkflowExecutionListener listener = + new WorkflowExecutionListener() { + @Override + public void onWorkflowCompleted(WorkflowCompletedEvent event) { + capturedOutput.set(event.output()); + } + }; + + try (WorkflowApplication app = WorkflowApplication.builder().withListener(listener).build()) { + WorkflowModel result = + app.workflowDefinition( + readWorkflowFromClasspath("workflows-samples/simple-expression.yaml")) + .instance(Map.of()) + .start() + .join(); + + assertThat(capturedOutput.get()) + .as("event.output() must be non-null in onWorkflowCompleted") + .isNotNull(); + assertThat(capturedOutput.get().asMap()) + .as("event.output() must equal the workflow's final output") + .isEqualTo(result.asMap()); + } + } +}