Skip to content

Commit bd8e2d9

Browse files
authored
Add ChildWorkflowOptions support to WorkflowImplementationOptions (#2)
- Add childWorkflowOptions map and defaultChildWorkflowOptions fields - Add setChildWorkflowOptions() and setDefaultChildWorkflowOptions() builder methods - Add getChildWorkflowOptions() and getDefaultChildWorkflowOptions() getters - Update SyncWorkflowContext to store and expose child workflow options - Update WorkflowInternal.newChildWorkflowStub() to merge predefined options - Add mergeChildWorkflowOptions() method to ChildWorkflowOptions.Builder - Add integration and unit tests --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 44bb603 commit bd8e2d9

6 files changed

Lines changed: 347 additions & 0 deletions

File tree

temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ final class SyncWorkflowContext implements WorkflowContext, WorkflowOutboundCall
100100
private Map<String, LocalActivityOptions> localActivityOptionsMap;
101101
private NexusServiceOptions defaultNexusServiceOptions = null;
102102
private Map<String, NexusServiceOptions> nexusServiceOptionsMap;
103+
private ChildWorkflowOptions defaultChildWorkflowOptions = null;
104+
private Map<String, ChildWorkflowOptions> childWorkflowOptionsMap;
103105
private boolean readOnly = false;
104106
private final WorkflowThreadLocal<UpdateInfo> currentUpdateInfo = new WorkflowThreadLocal<>();
105107
@Nullable private String currentDetails;
@@ -136,6 +138,10 @@ public SyncWorkflowContext(
136138
workflowImplementationOptions.getDefaultNexusServiceOptions();
137139
this.nexusServiceOptionsMap =
138140
new HashMap<>(workflowImplementationOptions.getNexusServiceOptions());
141+
this.defaultChildWorkflowOptions =
142+
workflowImplementationOptions.getDefaultChildWorkflowOptions();
143+
this.childWorkflowOptionsMap =
144+
new HashMap<>(workflowImplementationOptions.getChildWorkflowOptions());
139145
}
140146
this.workflowImplementationOptions =
141147
workflowImplementationOptions == null
@@ -215,6 +221,16 @@ public NexusServiceOptions getDefaultNexusServiceOptions() {
215221
: Collections.emptyMap();
216222
}
217223

224+
public ChildWorkflowOptions getDefaultChildWorkflowOptions() {
225+
return defaultChildWorkflowOptions;
226+
}
227+
228+
public @Nonnull Map<String, ChildWorkflowOptions> getChildWorkflowOptions() {
229+
return childWorkflowOptionsMap != null
230+
? Collections.unmodifiableMap(childWorkflowOptionsMap)
231+
: Collections.emptyMap();
232+
}
233+
218234
public void setDefaultActivityOptions(ActivityOptions defaultActivityOptions) {
219235
this.defaultActivityOptions =
220236
(this.defaultActivityOptions == null)

temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,27 @@ public static ActivityStub newUntypedLocalActivityStub(LocalActivityOptions opti
391391
@SuppressWarnings("unchecked")
392392
public static <T> T newChildWorkflowStub(
393393
Class<T> workflowInterface, ChildWorkflowOptions options) {
394+
SyncWorkflowContext context = getRootWorkflowContext();
395+
options = (options == null) ? context.getDefaultChildWorkflowOptions() : options;
396+
397+
POJOWorkflowInterfaceMetadata workflowMetadata =
398+
POJOWorkflowInterfaceMetadata.newInstance(workflowInterface);
399+
Optional<POJOWorkflowMethodMetadata> workflowMethodMetadata =
400+
workflowMetadata.getWorkflowMethod();
401+
if (workflowMethodMetadata.isPresent()) {
402+
String workflowType = workflowMethodMetadata.get().getName();
403+
@Nonnull
404+
Map<String, ChildWorkflowOptions> predefinedChildWorkflowOptions =
405+
context.getChildWorkflowOptions();
406+
ChildWorkflowOptions perTypeOptions = predefinedChildWorkflowOptions.get(workflowType);
407+
if (perTypeOptions != null) {
408+
options =
409+
ChildWorkflowOptions.newBuilder(perTypeOptions)
410+
.mergeChildWorkflowOptions(options)
411+
.build();
412+
}
413+
}
414+
394415
return (T)
395416
Proxy.newProxyInstance(
396417
workflowInterface.getClassLoader(),

temporal-sdk/src/main/java/io/temporal/worker/WorkflowImplementationOptions.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.temporal.activity.ActivityOptions;
44
import io.temporal.activity.LocalActivityOptions;
55
import io.temporal.common.Experimental;
6+
import io.temporal.workflow.ChildWorkflowOptions;
67
import io.temporal.workflow.NexusServiceOptions;
78
import io.temporal.workflow.Workflow;
89
import java.util.*;
@@ -42,6 +43,8 @@ public static final class Builder {
4243
private LocalActivityOptions defaultLocalActivityOptions;
4344
private Map<String, NexusServiceOptions> nexusServiceOptions;
4445
private NexusServiceOptions defaultNexusServiceOptions;
46+
private Map<String, ChildWorkflowOptions> childWorkflowOptions;
47+
private ChildWorkflowOptions defaultChildWorkflowOptions;
4548
private boolean enableUpsertVersionSearchAttributes;
4649

4750
private Builder() {}
@@ -57,6 +60,8 @@ private Builder(WorkflowImplementationOptions options) {
5760
this.defaultLocalActivityOptions = options.getDefaultLocalActivityOptions();
5861
this.nexusServiceOptions = options.getNexusServiceOptions();
5962
this.defaultNexusServiceOptions = options.getDefaultNexusServiceOptions();
63+
this.childWorkflowOptions = options.getChildWorkflowOptions();
64+
this.defaultChildWorkflowOptions = options.getDefaultChildWorkflowOptions();
6065
this.enableUpsertVersionSearchAttributes = options.isEnableUpsertVersionSearchAttributes();
6166
}
6267

@@ -158,6 +163,34 @@ public Builder setDefaultNexusServiceOptions(NexusServiceOptions defaultNexusSer
158163
return this;
159164
}
160165

166+
/**
167+
* Set individual child workflow options per workflow type. Will be merged with the options from
168+
* {@link io.temporal.workflow.Workflow#newChildWorkflowStub(Class, ChildWorkflowOptions)} which
169+
* has the highest precedence.
170+
*
171+
* @param childWorkflowOptions map from workflow type to ChildWorkflowOptions
172+
*/
173+
public Builder setChildWorkflowOptions(Map<String, ChildWorkflowOptions> childWorkflowOptions) {
174+
this.childWorkflowOptions = new HashMap<>(Objects.requireNonNull(childWorkflowOptions));
175+
return this;
176+
}
177+
178+
/**
179+
* These child workflow options have the lowest precedence across all child workflow options.
180+
* Will be overwritten entirely by {@link
181+
* io.temporal.workflow.Workflow#newChildWorkflowStub(Class, ChildWorkflowOptions)} and then by
182+
* the individual child workflow options if any are set through {@link
183+
* #setChildWorkflowOptions(Map)}
184+
*
185+
* @param defaultChildWorkflowOptions ChildWorkflowOptions for all child workflows in the
186+
* workflow.
187+
*/
188+
public Builder setDefaultChildWorkflowOptions(
189+
ChildWorkflowOptions defaultChildWorkflowOptions) {
190+
this.defaultChildWorkflowOptions = Objects.requireNonNull(defaultChildWorkflowOptions);
191+
return this;
192+
}
193+
161194
/**
162195
* Enable upserting version search attributes on {@link Workflow#getVersion}. This will cause
163196
* the SDK to automatically add the <b>TemporalChangeVersion</b> search attributes to the
@@ -187,6 +220,8 @@ public WorkflowImplementationOptions build() {
187220
defaultLocalActivityOptions,
188221
nexusServiceOptions == null ? null : nexusServiceOptions,
189222
defaultNexusServiceOptions,
223+
childWorkflowOptions == null ? null : childWorkflowOptions,
224+
defaultChildWorkflowOptions,
190225
enableUpsertVersionSearchAttributes);
191226
}
192227
}
@@ -198,6 +233,8 @@ public WorkflowImplementationOptions build() {
198233
private final LocalActivityOptions defaultLocalActivityOptions;
199234
private final @Nullable Map<String, NexusServiceOptions> nexusServiceOptions;
200235
private final NexusServiceOptions defaultNexusServiceOptions;
236+
private final @Nullable Map<String, ChildWorkflowOptions> childWorkflowOptions;
237+
private final ChildWorkflowOptions defaultChildWorkflowOptions;
201238
private final boolean enableUpsertVersionSearchAttributes;
202239

203240
public WorkflowImplementationOptions(
@@ -208,6 +245,8 @@ public WorkflowImplementationOptions(
208245
LocalActivityOptions defaultLocalActivityOptions,
209246
@Nullable Map<String, NexusServiceOptions> nexusServiceOptions,
210247
NexusServiceOptions defaultNexusServiceOptions,
248+
@Nullable Map<String, ChildWorkflowOptions> childWorkflowOptions,
249+
ChildWorkflowOptions defaultChildWorkflowOptions,
211250
boolean enableUpsertVersionSearchAttributes) {
212251
this.failWorkflowExceptionTypes = failWorkflowExceptionTypes;
213252
this.activityOptions = activityOptions;
@@ -216,6 +255,8 @@ public WorkflowImplementationOptions(
216255
this.defaultLocalActivityOptions = defaultLocalActivityOptions;
217256
this.nexusServiceOptions = nexusServiceOptions;
218257
this.defaultNexusServiceOptions = defaultNexusServiceOptions;
258+
this.childWorkflowOptions = childWorkflowOptions;
259+
this.defaultChildWorkflowOptions = defaultChildWorkflowOptions;
219260
this.enableUpsertVersionSearchAttributes = enableUpsertVersionSearchAttributes;
220261
}
221262

@@ -253,6 +294,16 @@ public NexusServiceOptions getDefaultNexusServiceOptions() {
253294
return defaultNexusServiceOptions;
254295
}
255296

297+
public @Nonnull Map<String, ChildWorkflowOptions> getChildWorkflowOptions() {
298+
return childWorkflowOptions != null
299+
? Collections.unmodifiableMap(childWorkflowOptions)
300+
: Collections.emptyMap();
301+
}
302+
303+
public ChildWorkflowOptions getDefaultChildWorkflowOptions() {
304+
return defaultChildWorkflowOptions;
305+
}
306+
256307
@Experimental
257308
public boolean isEnableUpsertVersionSearchAttributes() {
258309
return enableUpsertVersionSearchAttributes;
@@ -275,6 +326,10 @@ public String toString() {
275326
+ nexusServiceOptions
276327
+ ", defaultNexusServiceOptions="
277328
+ defaultNexusServiceOptions
329+
+ ", childWorkflowOptions="
330+
+ childWorkflowOptions
331+
+ ", defaultChildWorkflowOptions="
332+
+ defaultChildWorkflowOptions
278333
+ ", enableUpsertVersionSearchAttributes="
279334
+ enableUpsertVersionSearchAttributes
280335
+ '}';
@@ -292,6 +347,8 @@ public boolean equals(Object o) {
292347
&& Objects.equals(defaultLocalActivityOptions, that.defaultLocalActivityOptions)
293348
&& Objects.equals(nexusServiceOptions, that.nexusServiceOptions)
294349
&& Objects.equals(defaultNexusServiceOptions, that.defaultNexusServiceOptions)
350+
&& Objects.equals(childWorkflowOptions, that.childWorkflowOptions)
351+
&& Objects.equals(defaultChildWorkflowOptions, that.defaultChildWorkflowOptions)
295352
&& Objects.equals(
296353
enableUpsertVersionSearchAttributes, that.enableUpsertVersionSearchAttributes);
297354
}
@@ -306,6 +363,8 @@ public int hashCode() {
306363
defaultLocalActivityOptions,
307364
nexusServiceOptions,
308365
defaultNexusServiceOptions,
366+
childWorkflowOptions,
367+
defaultChildWorkflowOptions,
309368
enableUpsertVersionSearchAttributes);
310369
result = 31 * result + Arrays.hashCode(failWorkflowExceptionTypes);
311370
return result;

temporal-sdk/src/main/java/io/temporal/workflow/ChildWorkflowOptions.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,75 @@ public Builder setPriority(Priority priority) {
338338
return this;
339339
}
340340

341+
/**
342+
* Merges the provided override options into this builder. Any non-null fields in the override
343+
* will take precedence over the fields in this builder.
344+
*
345+
* @param override ChildWorkflowOptions that overrides the current builder values.
346+
* @return this builder.
347+
*/
348+
@SuppressWarnings("deprecation")
349+
public Builder mergeChildWorkflowOptions(ChildWorkflowOptions override) {
350+
if (override == null) {
351+
return this;
352+
}
353+
this.namespace = (override.getNamespace() == null) ? this.namespace : override.getNamespace();
354+
this.workflowId =
355+
(override.getWorkflowId() == null) ? this.workflowId : override.getWorkflowId();
356+
this.workflowIdReusePolicy =
357+
(override.getWorkflowIdReusePolicy() == null)
358+
? this.workflowIdReusePolicy
359+
: override.getWorkflowIdReusePolicy();
360+
this.workflowRunTimeout =
361+
(override.getWorkflowRunTimeout() == null)
362+
? this.workflowRunTimeout
363+
: override.getWorkflowRunTimeout();
364+
this.workflowExecutionTimeout =
365+
(override.getWorkflowExecutionTimeout() == null)
366+
? this.workflowExecutionTimeout
367+
: override.getWorkflowExecutionTimeout();
368+
this.workflowTaskTimeout =
369+
(override.getWorkflowTaskTimeout() == null)
370+
? this.workflowTaskTimeout
371+
: override.getWorkflowTaskTimeout();
372+
this.taskQueue = (override.getTaskQueue() == null) ? this.taskQueue : override.getTaskQueue();
373+
this.retryOptions =
374+
(override.getRetryOptions() == null) ? this.retryOptions : override.getRetryOptions();
375+
this.cronSchedule =
376+
(override.getCronSchedule() == null) ? this.cronSchedule : override.getCronSchedule();
377+
this.parentClosePolicy =
378+
(override.getParentClosePolicy() == null)
379+
? this.parentClosePolicy
380+
: override.getParentClosePolicy();
381+
this.memo = (override.getMemo() == null) ? this.memo : override.getMemo();
382+
this.searchAttributes =
383+
(override.getSearchAttributes() == null)
384+
? this.searchAttributes
385+
: override.getSearchAttributes();
386+
this.typedSearchAttributes =
387+
(override.getTypedSearchAttributes() == null)
388+
? this.typedSearchAttributes
389+
: override.getTypedSearchAttributes();
390+
this.contextPropagators =
391+
(override.getContextPropagators() == null)
392+
? this.contextPropagators
393+
: override.getContextPropagators();
394+
this.cancellationType =
395+
(override.getCancellationType() == null)
396+
? this.cancellationType
397+
: override.getCancellationType();
398+
this.versioningIntent =
399+
(override.getVersioningIntent() == null)
400+
? this.versioningIntent
401+
: override.getVersioningIntent();
402+
this.staticSummary =
403+
(override.getStaticSummary() == null) ? this.staticSummary : override.getStaticSummary();
404+
this.staticDetails =
405+
(override.getStaticDetails() == null) ? this.staticDetails : override.getStaticDetails();
406+
this.priority = (override.getPriority() == null) ? this.priority : override.getPriority();
407+
return this;
408+
}
409+
341410
public ChildWorkflowOptions build() {
342411
return new ChildWorkflowOptions(
343412
namespace,
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package io.temporal.workflow;
2+
3+
import static org.junit.Assert.*;
4+
5+
import io.temporal.worker.WorkflowImplementationOptions;
6+
import java.time.Duration;
7+
import java.util.Collections;
8+
import java.util.Map;
9+
import org.junit.Test;
10+
11+
public class ChildWorkflowOptionsInWorkflowImplementationOptionsTest {
12+
13+
@Test
14+
public void testBuilderSetAndGet() {
15+
ChildWorkflowOptions defaultOpts =
16+
ChildWorkflowOptions.newBuilder()
17+
.setWorkflowExecutionTimeout(Duration.ofSeconds(100))
18+
.setTaskQueue("default-queue")
19+
.build();
20+
21+
ChildWorkflowOptions perTypeOpts =
22+
ChildWorkflowOptions.newBuilder()
23+
.setWorkflowExecutionTimeout(Duration.ofSeconds(200))
24+
.setTaskQueue("per-type-queue")
25+
.build();
26+
27+
Map<String, ChildWorkflowOptions> optionsMap =
28+
Collections.singletonMap("MyWorkflow", perTypeOpts);
29+
30+
WorkflowImplementationOptions options =
31+
WorkflowImplementationOptions.newBuilder()
32+
.setDefaultChildWorkflowOptions(defaultOpts)
33+
.setChildWorkflowOptions(optionsMap)
34+
.build();
35+
36+
assertEquals(defaultOpts, options.getDefaultChildWorkflowOptions());
37+
assertEquals(1, options.getChildWorkflowOptions().size());
38+
assertEquals(perTypeOpts, options.getChildWorkflowOptions().get("MyWorkflow"));
39+
}
40+
41+
@Test
42+
public void testDefaultInstanceHasEmptyChildWorkflowOptions() {
43+
WorkflowImplementationOptions options = WorkflowImplementationOptions.getDefaultInstance();
44+
assertNull(options.getDefaultChildWorkflowOptions());
45+
assertNotNull(options.getChildWorkflowOptions());
46+
assertTrue(options.getChildWorkflowOptions().isEmpty());
47+
}
48+
49+
@Test
50+
public void testToBuilder() {
51+
ChildWorkflowOptions defaultOpts =
52+
ChildWorkflowOptions.newBuilder()
53+
.setWorkflowExecutionTimeout(Duration.ofSeconds(100))
54+
.build();
55+
56+
Map<String, ChildWorkflowOptions> optionsMap =
57+
Collections.singletonMap("MyWorkflow", defaultOpts);
58+
59+
WorkflowImplementationOptions original =
60+
WorkflowImplementationOptions.newBuilder()
61+
.setDefaultChildWorkflowOptions(defaultOpts)
62+
.setChildWorkflowOptions(optionsMap)
63+
.build();
64+
65+
WorkflowImplementationOptions copy = original.toBuilder().build();
66+
assertEquals(original.getDefaultChildWorkflowOptions(), copy.getDefaultChildWorkflowOptions());
67+
assertEquals(original.getChildWorkflowOptions(), copy.getChildWorkflowOptions());
68+
}
69+
70+
@Test
71+
public void testMergeChildWorkflowOptionsOverridesNonNull() {
72+
ChildWorkflowOptions base =
73+
ChildWorkflowOptions.newBuilder()
74+
.setWorkflowExecutionTimeout(Duration.ofSeconds(100))
75+
.setTaskQueue("base-queue")
76+
.setWorkflowRunTimeout(Duration.ofSeconds(50))
77+
.build();
78+
79+
ChildWorkflowOptions override =
80+
ChildWorkflowOptions.newBuilder()
81+
.setWorkflowExecutionTimeout(Duration.ofSeconds(200))
82+
.build();
83+
84+
ChildWorkflowOptions merged =
85+
ChildWorkflowOptions.newBuilder(base).mergeChildWorkflowOptions(override).build();
86+
87+
// Override takes precedence for workflowExecutionTimeout
88+
assertEquals(Duration.ofSeconds(200), merged.getWorkflowExecutionTimeout());
89+
// Base values are preserved for fields not set in override
90+
assertEquals("base-queue", merged.getTaskQueue());
91+
assertEquals(Duration.ofSeconds(50), merged.getWorkflowRunTimeout());
92+
}
93+
94+
@Test
95+
public void testMergeChildWorkflowOptionsWithNull() {
96+
ChildWorkflowOptions base =
97+
ChildWorkflowOptions.newBuilder()
98+
.setWorkflowExecutionTimeout(Duration.ofSeconds(100))
99+
.setTaskQueue("base-queue")
100+
.build();
101+
102+
ChildWorkflowOptions merged =
103+
ChildWorkflowOptions.newBuilder(base).mergeChildWorkflowOptions(null).build();
104+
105+
assertEquals(Duration.ofSeconds(100), merged.getWorkflowExecutionTimeout());
106+
assertEquals("base-queue", merged.getTaskQueue());
107+
}
108+
}

0 commit comments

Comments
 (0)