Skip to content

Commit a1be5ce

Browse files
mdproctorclaudefjtiradoCopilot
authored
Add Test and Java doc for WorkflowInstance.output (#1359)
* fix: move output() from WorkflowInstanceData to WorkflowInstance (#1357) output() and outputAs() are blocking operations (they join the workflow future) intended for callers outside the event chain — e.g. after instance.start().join(). Exposing them on WorkflowInstanceData, which is what WorkflowExecutionListener implementations see via event.workflowContext().instanceData(), misleads implementors into calling a blocking join from inside a callback. The correct API for accessing output inside onWorkflowCompleted is event.output(), which is populated directly from the task result before the event is published. Removes output() and outputAs() from WorkflowInstanceData. Declares them explicitly on WorkflowInstance, which extends WorkflowInstanceData and is the right type for post-completion callers. Adds WorkflowExecutionListenerOutputTest asserting that event.output() carries the correct value inside onWorkflowCompleted. Closes #1357 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: fjtirado <ftirados@redhat.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: fjtirado <ftirados@redhat.com> --------- Signed-off-by: fjtirado <ftirados@redhat.com> Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 1b0a475 commit a1be5ce

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,28 @@
2020
public interface WorkflowInstance extends WorkflowInstanceData {
2121
CompletableFuture<WorkflowModel> start();
2222

23+
/**
24+
* Returns the workflow output.
25+
*
26+
* <p>This method may block until the workflow execution has completed. Callers should not invoke
27+
* it from lifecycle callbacks, listener threads, or other execution contexts where blocking is
28+
* not safe.
29+
*
30+
* @return the workflow output
31+
*/
2332
WorkflowModel output();
2433

34+
/**
35+
* Returns the workflow output converted to the requested type.
36+
*
37+
* <p>This method may block until the workflow execution has completed. Callers should not invoke
38+
* it from lifecycle callbacks, listener threads, or other execution contexts where blocking is
39+
* not safe.
40+
*
41+
* @param clazz the target output type
42+
* @param <T> the target output type
43+
* @return the workflow output converted to {@code clazz}
44+
*/
2545
<T> T outputAs(Class<T> clazz);
2646

2747
boolean suspend();
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.test;
17+
18+
import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import io.serverlessworkflow.impl.WorkflowApplication;
22+
import io.serverlessworkflow.impl.WorkflowModel;
23+
import io.serverlessworkflow.impl.lifecycle.WorkflowCompletedEvent;
24+
import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener;
25+
import java.io.IOException;
26+
import java.util.Map;
27+
import java.util.concurrent.atomic.AtomicReference;
28+
import org.junit.jupiter.api.Test;
29+
30+
/**
31+
* Verifies that {@code event.output()} in {@code onWorkflowCompleted} carries the workflow's final
32+
* output. {@code WorkflowCompletedEvent.output()} is the correct API for accessing output inside
33+
* the hook — it is populated directly from the task result before the event is published.
34+
*
35+
* <p>{@code instanceData().output()} is intentionally absent from {@link
36+
* io.serverlessworkflow.impl.WorkflowInstanceData}: it is a blocking join on the workflow future
37+
* intended for callers outside the event chain (e.g. after {@code instance.start().join()}).
38+
*/
39+
class WorkflowExecutionListenerOutputTest {
40+
41+
@Test
42+
void eventOutputIsPopulatedInOnWorkflowCompleted() throws IOException {
43+
AtomicReference<WorkflowModel> capturedOutput = new AtomicReference<>();
44+
45+
WorkflowExecutionListener listener =
46+
new WorkflowExecutionListener() {
47+
@Override
48+
public void onWorkflowCompleted(WorkflowCompletedEvent event) {
49+
capturedOutput.set(event.output());
50+
}
51+
};
52+
53+
try (WorkflowApplication app = WorkflowApplication.builder().withListener(listener).build()) {
54+
WorkflowModel result =
55+
app.workflowDefinition(
56+
readWorkflowFromClasspath("workflows-samples/simple-expression.yaml"))
57+
.instance(Map.of())
58+
.start()
59+
.join();
60+
61+
assertThat(capturedOutput.get())
62+
.as("event.output() must be non-null in onWorkflowCompleted")
63+
.isNotNull();
64+
assertThat(capturedOutput.get().asMap())
65+
.as("event.output() must equal the workflow's final output")
66+
.isEqualTo(result.asMap());
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)