Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ class ChildWorkflowInvocationHandler implements InvocationHandler {
private final POJOWorkflowInterfaceMetadata workflowMetadata;

ChildWorkflowInvocationHandler(
Class<?> workflowInterface,
POJOWorkflowInterfaceMetadata workflowMetadata,
ChildWorkflowOptions options,
WorkflowOutboundCallsInterceptor outboundCallsInterceptor,
Functions.Proc1<String> assertReadOnly) {
workflowMetadata = POJOWorkflowInterfaceMetadata.newInstance(workflowInterface);
this.workflowMetadata = workflowMetadata;
Optional<POJOWorkflowMethodMetadata> workflowMethodMetadata =
workflowMetadata.getWorkflowMethod();
if (!workflowMethodMetadata.isPresent()) {
throw new IllegalArgumentException(
"Missing method annotated with @WorkflowMethod: " + workflowInterface.getName());
"Missing method annotated with @WorkflowMethod: "
+ workflowMetadata.getInterfaceClass().getName());
}
Method workflowMethod = workflowMethodMetadata.get().getWorkflowMethod();
MethodRetry retryAnnotation = workflowMethod.getAnnotation(MethodRetry.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,18 @@ public NexusServiceOptions getDefaultNexusServiceOptions() {
: Collections.emptyMap();
}

/**
* Unlike the activity options above, the child workflow options have no runtime mutators, so they
* are served directly from the immutable {@link WorkflowImplementationOptions}.
*/
public ChildWorkflowOptions getDefaultChildWorkflowOptions() {
return workflowImplementationOptions.getDefaultChildWorkflowOptions();
}

public @Nonnull Map<String, ChildWorkflowOptions> getChildWorkflowOptions() {
return workflowImplementationOptions.getChildWorkflowOptions();
}

public void setDefaultActivityOptions(ActivityOptions defaultActivityOptions) {
this.defaultActivityOptions =
(this.defaultActivityOptions == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,17 +391,48 @@ public static ActivityStub newUntypedLocalActivityStub(LocalActivityOptions opti
@SuppressWarnings("unchecked")
public static <T> T newChildWorkflowStub(
Class<T> workflowInterface, ChildWorkflowOptions options) {
SyncWorkflowContext context = getRootWorkflowContext();
POJOWorkflowInterfaceMetadata workflowMetadata =
POJOWorkflowInterfaceMetadata.newInstance(workflowInterface);
// The workflow type is absent for interfaces that contain only signal and query methods;
// ChildWorkflowInvocationHandler rejects such interfaces.
String workflowType = workflowMetadata.getWorkflowType().orElse(null);
options = mergePredefinedChildWorkflowOptions(context, workflowType, options);
return (T)
Proxy.newProxyInstance(
workflowInterface.getClassLoader(),
new Class<?>[] {workflowInterface, StubMarker.class, AsyncMarker.class},
new ChildWorkflowInvocationHandler(
workflowInterface,
workflowMetadata,
options,
getWorkflowOutboundInterceptor(),
context.getWorkflowOutboundInterceptor(),
WorkflowInternal::assertNotReadOnly));
}

/**
* Merges the child workflow options predefined through {@link
* io.temporal.worker.WorkflowImplementationOptions.Builder#setChildWorkflowOptions(Map)} and
* {@link io.temporal.worker.WorkflowImplementationOptions.Builder#setDefaultChildWorkflowOptions(
* ChildWorkflowOptions)} into the options passed to the stub creation method. Precedence from
* lowest to highest: default options < per-type options < the passed options. Each layer
* overrides only the non-null fields of the layers below it.
*/
private static ChildWorkflowOptions mergePredefinedChildWorkflowOptions(
SyncWorkflowContext context,
@Nullable String workflowType,
@Nullable ChildWorkflowOptions options) {
ChildWorkflowOptions defaultOptions = context.getDefaultChildWorkflowOptions();
ChildWorkflowOptions perTypeOptions =
workflowType != null ? context.getChildWorkflowOptions().get(workflowType) : null;
if (defaultOptions == null && perTypeOptions == null) {
return options;
}
return ChildWorkflowOptions.newBuilder(defaultOptions)
.mergeChildWorkflowOptions(perTypeOptions)
.mergeChildWorkflowOptions(options)
.build();
}

@SuppressWarnings("unchecked")
public static <T> T newExternalWorkflowStub(
Class<T> workflowInterface, WorkflowExecution execution) {
Expand Down Expand Up @@ -434,10 +465,12 @@ public static Promise<WorkflowExecution> getWorkflowExecution(Object workflowStu

public static ChildWorkflowStub newUntypedChildWorkflowStub(
String workflowType, ChildWorkflowOptions options) {
SyncWorkflowContext context = getRootWorkflowContext();
options = mergePredefinedChildWorkflowOptions(context, workflowType, options);
return new ChildWorkflowStubImpl(
workflowType,
options,
getWorkflowOutboundInterceptor(),
context.getWorkflowOutboundInterceptor(),
WorkflowInternal::assertNotReadOnly);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.temporal.activity.ActivityOptions;
import io.temporal.activity.LocalActivityOptions;
import io.temporal.common.Experimental;
import io.temporal.workflow.ChildWorkflowOptions;
import io.temporal.workflow.NexusServiceOptions;
import io.temporal.workflow.Workflow;
import java.util.*;
Expand Down Expand Up @@ -42,6 +43,8 @@ public static final class Builder {
private LocalActivityOptions defaultLocalActivityOptions;
private Map<String, NexusServiceOptions> nexusServiceOptions;
private NexusServiceOptions defaultNexusServiceOptions;
private Map<String, ChildWorkflowOptions> childWorkflowOptions;
private ChildWorkflowOptions defaultChildWorkflowOptions;
private boolean enableUpsertVersionSearchAttributes;

private Builder() {}
Expand All @@ -57,6 +60,8 @@ private Builder(WorkflowImplementationOptions options) {
this.defaultLocalActivityOptions = options.getDefaultLocalActivityOptions();
this.nexusServiceOptions = options.getNexusServiceOptions();
this.defaultNexusServiceOptions = options.getDefaultNexusServiceOptions();
this.childWorkflowOptions = options.getChildWorkflowOptions();
this.defaultChildWorkflowOptions = options.getDefaultChildWorkflowOptions();
this.enableUpsertVersionSearchAttributes = options.isEnableUpsertVersionSearchAttributes();
}

Expand Down Expand Up @@ -158,6 +163,49 @@ public Builder setDefaultNexusServiceOptions(NexusServiceOptions defaultNexusSer
return this;
}

/**
* Set individual child workflow options per workflow type. They apply to child workflow stubs
* created through both {@link io.temporal.workflow.Workflow#newChildWorkflowStub(Class,
* ChildWorkflowOptions)} and {@link
* io.temporal.workflow.Workflow#newUntypedChildWorkflowStub(String, ChildWorkflowOptions)}.
* These options take precedence over the default options set through {@link
* #setDefaultChildWorkflowOptions(ChildWorkflowOptions)}, but each field is still overridden by
* the corresponding non-null field of the options passed to the stub creation method, which
* have the highest precedence.
*
* <p>Avoid setting {@link ChildWorkflowOptions.Builder#setWorkflowId(String)} here: the id
* would be applied to every child workflow of the type, so starting more than one such child
* fails with a duplicate workflow id error.
*
* @param childWorkflowOptions map from workflow type to ChildWorkflowOptions
*/
public Builder setChildWorkflowOptions(Map<String, ChildWorkflowOptions> childWorkflowOptions) {
this.childWorkflowOptions = new HashMap<>(Objects.requireNonNull(childWorkflowOptions));
return this;
}

/**
* These child workflow options have the lowest precedence across all child workflow options.
* They apply to child workflow stubs created through both {@link
* io.temporal.workflow.Workflow#newChildWorkflowStub(Class, ChildWorkflowOptions)} and {@link
* io.temporal.workflow.Workflow#newUntypedChildWorkflowStub(String, ChildWorkflowOptions)}.
* Each field is overridden by the corresponding non-null field of the per-type options set
* through {@link #setChildWorkflowOptions(Map)}, and then by the options passed to the stub
* creation method, which have the highest precedence.
*
* <p>Avoid setting {@link ChildWorkflowOptions.Builder#setWorkflowId(String)} here: the id
* would be applied to every child workflow started with these options, so starting more than
* one such child fails with a duplicate workflow id error.
*
* @param defaultChildWorkflowOptions ChildWorkflowOptions for all child workflows in the
* workflow.
*/
public Builder setDefaultChildWorkflowOptions(
ChildWorkflowOptions defaultChildWorkflowOptions) {
this.defaultChildWorkflowOptions = Objects.requireNonNull(defaultChildWorkflowOptions);
return this;
}

/**
* Enable upserting version search attributes on {@link Workflow#getVersion}. This will cause
* the SDK to automatically add the <b>TemporalChangeVersion</b> search attributes to the
Expand Down Expand Up @@ -187,6 +235,8 @@ public WorkflowImplementationOptions build() {
defaultLocalActivityOptions,
nexusServiceOptions == null ? null : nexusServiceOptions,
defaultNexusServiceOptions,
childWorkflowOptions,
defaultChildWorkflowOptions,
enableUpsertVersionSearchAttributes);
}
}
Expand All @@ -198,8 +248,14 @@ public WorkflowImplementationOptions build() {
private final LocalActivityOptions defaultLocalActivityOptions;
private final @Nullable Map<String, NexusServiceOptions> nexusServiceOptions;
private final NexusServiceOptions defaultNexusServiceOptions;
private final @Nullable Map<String, ChildWorkflowOptions> childWorkflowOptions;
private final ChildWorkflowOptions defaultChildWorkflowOptions;
private final boolean enableUpsertVersionSearchAttributes;

/**
* Retained for backward compatibility with code compiled against SDK versions without child
* workflow options support. Prefer {@link #newBuilder()}.
*/
public WorkflowImplementationOptions(
Class<? extends Throwable>[] failWorkflowExceptionTypes,
@Nullable Map<String, ActivityOptions> activityOptions,
Expand All @@ -209,13 +265,39 @@ public WorkflowImplementationOptions(
@Nullable Map<String, NexusServiceOptions> nexusServiceOptions,
NexusServiceOptions defaultNexusServiceOptions,
boolean enableUpsertVersionSearchAttributes) {
this(
failWorkflowExceptionTypes,
activityOptions,
defaultActivityOptions,
localActivityOptions,
defaultLocalActivityOptions,
nexusServiceOptions,
defaultNexusServiceOptions,
null,
null,
enableUpsertVersionSearchAttributes);
}

public WorkflowImplementationOptions(
Class<? extends Throwable>[] failWorkflowExceptionTypes,
@Nullable Map<String, ActivityOptions> activityOptions,
ActivityOptions defaultActivityOptions,
@Nullable Map<String, LocalActivityOptions> localActivityOptions,
LocalActivityOptions defaultLocalActivityOptions,
@Nullable Map<String, NexusServiceOptions> nexusServiceOptions,
NexusServiceOptions defaultNexusServiceOptions,
@Nullable Map<String, ChildWorkflowOptions> childWorkflowOptions,
ChildWorkflowOptions defaultChildWorkflowOptions,
boolean enableUpsertVersionSearchAttributes) {
this.failWorkflowExceptionTypes = failWorkflowExceptionTypes;
this.activityOptions = activityOptions;
this.defaultActivityOptions = defaultActivityOptions;
this.localActivityOptions = localActivityOptions;
this.defaultLocalActivityOptions = defaultLocalActivityOptions;
this.nexusServiceOptions = nexusServiceOptions;
this.defaultNexusServiceOptions = defaultNexusServiceOptions;
this.childWorkflowOptions = childWorkflowOptions;
this.defaultChildWorkflowOptions = defaultChildWorkflowOptions;
this.enableUpsertVersionSearchAttributes = enableUpsertVersionSearchAttributes;
}

Expand Down Expand Up @@ -253,6 +335,16 @@ public NexusServiceOptions getDefaultNexusServiceOptions() {
return defaultNexusServiceOptions;
}

public @Nonnull Map<String, ChildWorkflowOptions> getChildWorkflowOptions() {
return childWorkflowOptions != null
? Collections.unmodifiableMap(childWorkflowOptions)
: Collections.emptyMap();
}

public ChildWorkflowOptions getDefaultChildWorkflowOptions() {
return defaultChildWorkflowOptions;
}

@Experimental
public boolean isEnableUpsertVersionSearchAttributes() {
return enableUpsertVersionSearchAttributes;
Expand All @@ -275,6 +367,10 @@ public String toString() {
+ nexusServiceOptions
+ ", defaultNexusServiceOptions="
+ defaultNexusServiceOptions
+ ", childWorkflowOptions="
+ childWorkflowOptions
+ ", defaultChildWorkflowOptions="
+ defaultChildWorkflowOptions
+ ", enableUpsertVersionSearchAttributes="
+ enableUpsertVersionSearchAttributes
+ '}';
Expand All @@ -292,6 +388,8 @@ public boolean equals(Object o) {
&& Objects.equals(defaultLocalActivityOptions, that.defaultLocalActivityOptions)
&& Objects.equals(nexusServiceOptions, that.nexusServiceOptions)
&& Objects.equals(defaultNexusServiceOptions, that.defaultNexusServiceOptions)
&& Objects.equals(childWorkflowOptions, that.childWorkflowOptions)
&& Objects.equals(defaultChildWorkflowOptions, that.defaultChildWorkflowOptions)
&& Objects.equals(
enableUpsertVersionSearchAttributes, that.enableUpsertVersionSearchAttributes);
}
Expand All @@ -306,6 +404,8 @@ public int hashCode() {
defaultLocalActivityOptions,
nexusServiceOptions,
defaultNexusServiceOptions,
childWorkflowOptions,
defaultChildWorkflowOptions,
enableUpsertVersionSearchAttributes);
result = 31 * result + Arrays.hashCode(failWorkflowExceptionTypes);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.temporal.failure.TimeoutFailure;
import io.temporal.internal.common.OptionsUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -338,6 +339,89 @@ public Builder setPriority(Priority priority) {
return this;
}

/**
* Merges the provided override options into this builder. Any non-null fields in the override
* will take precedence over the fields in this builder, with the following exceptions:
*
* <ul>
* <li>{@code contextPropagators} lists are concatenated instead of replaced, matching {@link
* io.temporal.activity.ActivityOptions.Builder#mergeActivityOptions(
* io.temporal.activity.ActivityOptions)}.
* <li>The mutually exclusive {@code searchAttributes} and {@code typedSearchAttributes} are
* merged as a single logical field: an override that specifies either flavor replaces
* both, so the merged options never carry both flavors at once.
* <li>A {@code versioningIntent} of {@link VersioningIntent#VERSIONING_INTENT_UNSPECIFIED} is
* treated as unset.
* </ul>
*
* @param override ChildWorkflowOptions that overrides the current builder values.
* @return this builder.
*/
@SuppressWarnings("deprecation")
public Builder mergeChildWorkflowOptions(ChildWorkflowOptions override) {
if (override == null) {
return this;
}
this.namespace = (override.getNamespace() == null) ? this.namespace : override.getNamespace();
this.workflowId =
(override.getWorkflowId() == null) ? this.workflowId : override.getWorkflowId();
this.workflowIdReusePolicy =
(override.getWorkflowIdReusePolicy() == null)
? this.workflowIdReusePolicy
: override.getWorkflowIdReusePolicy();
this.workflowRunTimeout =
(override.getWorkflowRunTimeout() == null)
? this.workflowRunTimeout
: override.getWorkflowRunTimeout();
this.workflowExecutionTimeout =
(override.getWorkflowExecutionTimeout() == null)
? this.workflowExecutionTimeout
: override.getWorkflowExecutionTimeout();
this.workflowTaskTimeout =
(override.getWorkflowTaskTimeout() == null)
? this.workflowTaskTimeout
: override.getWorkflowTaskTimeout();
this.taskQueue = (override.getTaskQueue() == null) ? this.taskQueue : override.getTaskQueue();
this.retryOptions =
(override.getRetryOptions() == null) ? this.retryOptions : override.getRetryOptions();
this.cronSchedule =
(override.getCronSchedule() == null) ? this.cronSchedule : override.getCronSchedule();
this.parentClosePolicy =
(override.getParentClosePolicy() == null)
? this.parentClosePolicy
: override.getParentClosePolicy();
this.memo = (override.getMemo() == null) ? this.memo : override.getMemo();
// searchAttributes and typedSearchAttributes are mutually exclusive (the setters reject
// mixing them), so they are merged as one logical field: an override specifying either
// flavor replaces both, otherwise the merged options could carry both flavors and fail at
// child-scheduling time.
if (override.getSearchAttributes() != null || override.getTypedSearchAttributes() != null) {
this.searchAttributes = override.getSearchAttributes();
this.typedSearchAttributes = override.getTypedSearchAttributes();
}
if (this.contextPropagators == null) {
this.contextPropagators = override.getContextPropagators();
} else if (override.getContextPropagators() != null) {
List<ContextPropagator> mergedPropagators = new ArrayList<>(this.contextPropagators);
mergedPropagators.addAll(override.getContextPropagators());
this.contextPropagators = mergedPropagators;
}
this.cancellationType =
(override.getCancellationType() == null)
? this.cancellationType
: override.getCancellationType();
if (override.getVersioningIntent() != null
&& override.getVersioningIntent() != VersioningIntent.VERSIONING_INTENT_UNSPECIFIED) {
this.versioningIntent = override.getVersioningIntent();
}
this.staticSummary =
(override.getStaticSummary() == null) ? this.staticSummary : override.getStaticSummary();
this.staticDetails =
(override.getStaticDetails() == null) ? this.staticDetails : override.getStaticDetails();
this.priority = (override.getPriority() == null) ? this.priority : override.getPriority();
return this;
}

public ChildWorkflowOptions build() {
return new ChildWorkflowOptions(
namespace,
Expand Down
Loading