Skip to content

Commit 21d15ae

Browse files
Add workflow init support (#2222)
Add workflow Init support
1 parent 03f7182 commit 21d15ae

15 files changed

+713
-53
lines changed

temporal-sdk/src/main/java/io/temporal/common/metadata/POJOWorkflowImplMetadata.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222

2323
import com.google.common.collect.ImmutableList;
2424
import io.temporal.common.Experimental;
25+
import io.temporal.internal.common.env.ReflectionUtils;
26+
import java.lang.reflect.Constructor;
2527
import java.util.*;
28+
import java.util.stream.Collectors;
29+
import javax.annotation.Nullable;
2630

2731
/**
2832
* Rules:
@@ -69,6 +73,7 @@ public int hashCode() {
6973
private final List<POJOWorkflowMethodMetadata> queryMethods;
7074
private final List<POJOWorkflowMethodMetadata> updateMethods;
7175
private final List<POJOWorkflowMethodMetadata> updateValidatorMethods;
76+
private final Constructor<?> workflowInit;
7277

7378
/**
7479
* Create POJOWorkflowImplMetadata for a workflow implementation class. The object must implement
@@ -161,6 +166,17 @@ private POJOWorkflowImplMetadata(Class<?> implClass, boolean listener) {
161166
this.queryMethods = ImmutableList.copyOf(queryMethods.values());
162167
this.updateMethods = ImmutableList.copyOf(updateMethods.values());
163168
this.updateValidatorMethods = ImmutableList.copyOf(updateValidatorMethods.values());
169+
if (!listener) {
170+
this.workflowInit =
171+
ReflectionUtils.getConstructor(
172+
implClass,
173+
this.workflowMethods.stream()
174+
.map(POJOWorkflowMethodMetadata::getWorkflowMethod)
175+
.collect(Collectors.toList()))
176+
.orElse(null);
177+
} else {
178+
this.workflowInit = null;
179+
}
164180
}
165181

166182
/** List of workflow interfaces an object implements. */
@@ -194,4 +210,9 @@ public List<POJOWorkflowMethodMetadata> getUpdateMethods() {
194210
public List<POJOWorkflowMethodMetadata> getUpdateValidatorMethods() {
195211
return updateValidatorMethods;
196212
}
213+
214+
@Experimental
215+
public @Nullable Constructor<?> getWorkflowInit() {
216+
return workflowInit;
217+
}
197218
}

temporal-sdk/src/main/java/io/temporal/internal/common/env/ReflectionUtils.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,69 @@
2121
package io.temporal.internal.common.env;
2222

2323
import com.google.common.base.Joiner;
24+
import io.temporal.workflow.WorkflowInit;
25+
import java.lang.reflect.Constructor;
26+
import java.lang.reflect.Method;
27+
import java.lang.reflect.Modifier;
28+
import java.util.Arrays;
29+
import java.util.List;
30+
import java.util.Optional;
2431

2532
public final class ReflectionUtils {
2633
private ReflectionUtils() {}
2734

35+
public static Optional<Constructor<?>> getConstructor(
36+
Class<?> clazz, List<Method> workflowMethod) {
37+
// We iterate through all constructors to find the one annotated with @WorkflowInit
38+
// and check if it has the same parameters as the workflow method.
39+
// We check all declared constructors to find any constructors that are annotated with
40+
// @WorkflowInit, but not public,
41+
// to give a more informative error message.
42+
Optional<Constructor<?>> workflowInit = Optional.empty();
43+
Constructor<?> defaultConstructors = null;
44+
for (Constructor<?> ctor : clazz.getDeclaredConstructors()) {
45+
WorkflowInit wfInit = ctor.getAnnotation(WorkflowInit.class);
46+
if (wfInit == null) {
47+
if (ctor.getParameterCount() == 0 && Modifier.isPublic(ctor.getModifiers())) {
48+
if (workflowInit.isPresent() || defaultConstructors != null) {
49+
throw new IllegalArgumentException(
50+
"Multiple constructors annotated with @WorkflowInit or a default constructor found: "
51+
+ clazz.getName());
52+
}
53+
defaultConstructors = ctor;
54+
continue;
55+
}
56+
continue;
57+
}
58+
if (workflowMethod.size() != 1) {
59+
throw new IllegalArgumentException(
60+
"Multiple interfaces implemented while using @WorkflowInit annotation. Only one is allowed: "
61+
+ clazz.getName());
62+
}
63+
if (workflowInit.isPresent() || defaultConstructors != null) {
64+
throw new IllegalArgumentException(
65+
"Multiple constructors annotated with @WorkflowInit or a default constructor found: "
66+
+ clazz.getName());
67+
}
68+
if (!Modifier.isPublic(ctor.getModifiers())) {
69+
throw new IllegalArgumentException(
70+
"Constructor with @WorkflowInit annotation must be public: " + clazz.getName());
71+
}
72+
if (!Arrays.equals(ctor.getParameterTypes(), workflowMethod.get(0).getParameterTypes())) {
73+
throw new IllegalArgumentException(
74+
"Constructor annotated with @WorkflowInit must have the same parameters as the workflow method: "
75+
+ clazz.getName());
76+
}
77+
workflowInit = Optional.of(ctor);
78+
}
79+
if (!workflowInit.isPresent() && defaultConstructors == null) {
80+
throw new IllegalArgumentException(
81+
"No default constructor or constructor annotated with @WorkflowInit found: "
82+
+ clazz.getName());
83+
}
84+
return workflowInit;
85+
}
86+
2887
public static String getMethodNameForStackTraceCutoff(
2988
Class<?> clazz, String methodName, Class<?>... parameterTypes) throws RuntimeException {
3089
try {

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@
3434

3535
final class DynamicSyncWorkflowDefinition implements SyncWorkflowDefinition {
3636

37-
private final Functions.Func<? extends DynamicWorkflow> factory;
37+
private final Functions.Func1<EncodedValues, ? extends DynamicWorkflow> factory;
3838
private final WorkerInterceptor[] workerInterceptors;
3939
// don't pass it down to other classes, it's a "cached" instance for internal usage only
4040
private final DataConverter dataConverterWithWorkflowContext;
4141
private WorkflowInboundCallsInterceptor workflowInvoker;
4242

4343
public DynamicSyncWorkflowDefinition(
44-
Functions.Func<? extends DynamicWorkflow> factory,
44+
Functions.Func1<EncodedValues, ? extends DynamicWorkflow> factory,
4545
WorkerInterceptor[] workerInterceptors,
4646
DataConverter dataConverterWithWorkflowContext) {
4747
this.factory = factory;
@@ -50,9 +50,9 @@ public DynamicSyncWorkflowDefinition(
5050
}
5151

5252
@Override
53-
public void initialize() {
53+
public void initialize(Optional<Payloads> input) {
5454
SyncWorkflowContext workflowContext = WorkflowInternal.getRootWorkflowContext();
55-
workflowInvoker = new RootWorkflowInboundCallsInterceptor(workflowContext);
55+
workflowInvoker = new RootWorkflowInboundCallsInterceptor(workflowContext, input);
5656
for (WorkerInterceptor workerInterceptor : workerInterceptors) {
5757
workflowInvoker = workerInterceptor.interceptWorkflow(workflowInvoker);
5858
}
@@ -71,15 +71,18 @@ public Optional<Payloads> execute(Header header, Optional<Payloads> input) {
7171

7272
class RootWorkflowInboundCallsInterceptor extends BaseRootWorkflowInboundCallsInterceptor {
7373
private DynamicWorkflow workflow;
74+
private Optional<Payloads> input;
7475

75-
public RootWorkflowInboundCallsInterceptor(SyncWorkflowContext workflowContext) {
76+
public RootWorkflowInboundCallsInterceptor(
77+
SyncWorkflowContext workflowContext, Optional<Payloads> input) {
7678
super(workflowContext);
79+
this.input = input;
7780
}
7881

7982
@Override
8083
public void init(WorkflowOutboundCallsInterceptor outboundCalls) {
8184
super.init(outboundCalls);
82-
newInstance();
85+
newInstance(input);
8386
WorkflowInternal.registerListener(workflow);
8487
}
8588

@@ -89,11 +92,11 @@ public WorkflowOutput execute(WorkflowInput input) {
8992
return new WorkflowOutput(result);
9093
}
9194

92-
private void newInstance() {
95+
private void newInstance(Optional<Payloads> input) {
9396
if (workflow != null) {
9497
throw new IllegalStateException("Already called");
9598
}
96-
workflow = factory.apply();
99+
workflow = factory.apply(new EncodedValues(input, dataConverterWithWorkflowContext));
97100
}
98101
}
99102
}

0 commit comments

Comments
 (0)