• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

temporalio / sdk-java / #169

pending completion
#169

push

github-actions

web-flow
Remove use of deprecated API (#1758)

4 of 4 new or added lines in 1 file covered. (100.0%)

17345 of 21558 relevant lines covered (80.46%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.71
/temporal-sdk/src/main/java/io/temporal/client/WorkflowInvocationHandler.java
1
/*
2
 * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3
 *
4
 * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
 *
6
 * Modifications copyright (C) 2017 Uber Technologies, Inc.
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this material except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *   http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20

21
package io.temporal.client;
22

23
import com.google.common.base.Defaults;
24
import io.temporal.api.common.v1.WorkflowExecution;
25
import io.temporal.api.enums.v1.WorkflowIdReusePolicy;
26
import io.temporal.common.CronSchedule;
27
import io.temporal.common.MethodRetry;
28
import io.temporal.common.interceptors.WorkflowClientCallsInterceptor;
29
import io.temporal.common.interceptors.WorkflowClientInterceptor;
30
import io.temporal.common.metadata.POJOWorkflowInterfaceMetadata;
31
import io.temporal.common.metadata.POJOWorkflowMethodMetadata;
32
import io.temporal.common.metadata.WorkflowMethodType;
33
import io.temporal.internal.sync.StubMarker;
34
import io.temporal.workflow.QueryMethod;
35
import io.temporal.workflow.SignalMethod;
36
import io.temporal.workflow.WorkflowMethod;
37
import java.lang.reflect.InvocationHandler;
38
import java.lang.reflect.Method;
39
import java.util.Objects;
40
import java.util.Optional;
41

42
/**
43
 * Dynamic implementation of a strongly typed workflow interface that can be used to start, signal
44
 * and query workflows from external processes.
45
 */
46
class WorkflowInvocationHandler implements InvocationHandler {
47

48
  public enum InvocationType {
1✔
49
    SYNC,
1✔
50
    START,
1✔
51
    EXECUTE,
1✔
52
    SIGNAL_WITH_START,
1✔
53
  }
54

55
  interface SpecificInvocationHandler {
56
    InvocationType getInvocationType();
57

58
    void invoke(
59
        POJOWorkflowInterfaceMetadata workflowMetadata,
60
        WorkflowStub untyped,
61
        Method method,
62
        Object[] args)
63
        throws Throwable;
64

65
    <R> R getResult(Class<R> resultClass);
66
  }
67

68
  private static final ThreadLocal<SpecificInvocationHandler> invocationContext =
1✔
69
      new ThreadLocal<>();
70

71
  /** Must call {@link #closeAsyncInvocation()} if this one was called. */
72
  static void initAsyncInvocation(InvocationType type) {
73
    initAsyncInvocation(type, null);
1✔
74
  }
1✔
75

76
  /** Must call {@link #closeAsyncInvocation()} if this one was called. */
77
  static <T> void initAsyncInvocation(InvocationType type, T value) {
78
    if (invocationContext.get() != null) {
1✔
79
      throw new IllegalStateException("already in start invocation");
×
80
    }
81
    if (type == InvocationType.START) {
1✔
82
      invocationContext.set(new StartWorkflowInvocationHandler());
1✔
83
    } else if (type == InvocationType.EXECUTE) {
1✔
84
      invocationContext.set(new ExecuteWorkflowInvocationHandler());
1✔
85
    } else if (type == InvocationType.SIGNAL_WITH_START) {
1✔
86
      SignalWithStartBatchRequest batch = (SignalWithStartBatchRequest) value;
1✔
87
      invocationContext.set(new SignalWithStartWorkflowInvocationHandler(batch));
1✔
88
    } else {
1✔
89
      throw new IllegalArgumentException("Unexpected InvocationType: " + type);
×
90
    }
91
  }
1✔
92

93
  static <R> R getAsyncInvocationResult(Class<R> resultClass) {
94
    SpecificInvocationHandler invocation = invocationContext.get();
1✔
95
    if (invocation == null) {
1✔
96
      throw new IllegalStateException("initAsyncInvocation wasn't called");
×
97
    }
98
    return invocation.getResult(resultClass);
1✔
99
  }
100

101
  /** Closes async invocation created through {@link #initAsyncInvocation(InvocationType)} */
102
  static void closeAsyncInvocation() {
103
    invocationContext.remove();
1✔
104
  }
1✔
105

106
  private final WorkflowStub untyped;
107
  private final POJOWorkflowInterfaceMetadata workflowMetadata;
108

109
  @SuppressWarnings("deprecation")
110
  WorkflowInvocationHandler(
111
      Class<?> workflowInterface,
112
      WorkflowClientOptions clientOptions,
113
      WorkflowClientCallsInterceptor workflowClientCallsInvoker,
114
      WorkflowExecution execution) {
1✔
115
    workflowMetadata = POJOWorkflowInterfaceMetadata.newInstance(workflowInterface, false);
1✔
116
    Optional<String> workflowType = workflowMetadata.getWorkflowType();
1✔
117
    WorkflowStub stub =
1✔
118
        new WorkflowStubImpl(clientOptions, workflowClientCallsInvoker, workflowType, execution);
119
    for (WorkflowClientInterceptor i : clientOptions.getInterceptors()) {
1✔
120
      stub = i.newUntypedWorkflowStub(execution, workflowType, stub);
1✔
121
    }
122
    this.untyped = stub;
1✔
123
  }
1✔
124

125
  @SuppressWarnings("deprecation")
126
  WorkflowInvocationHandler(
127
      Class<?> workflowInterface,
128
      WorkflowClientOptions clientOptions,
129
      WorkflowClientCallsInterceptor workflowClientCallsInvoker,
130
      WorkflowOptions options) {
1✔
131
    Objects.requireNonNull(options, "options");
1✔
132
    workflowMetadata = POJOWorkflowInterfaceMetadata.newInstance(workflowInterface);
1✔
133
    Optional<POJOWorkflowMethodMetadata> workflowMethodMetadata =
1✔
134
        workflowMetadata.getWorkflowMethod();
1✔
135
    if (!workflowMethodMetadata.isPresent()) {
1✔
136
      throw new IllegalArgumentException(
×
137
          "Method annotated with @WorkflowMethod is not found in " + workflowInterface);
138
    }
139
    Method workflowMethod = workflowMethodMetadata.get().getWorkflowMethod();
1✔
140
    MethodRetry methodRetry = workflowMethod.getAnnotation(MethodRetry.class);
1✔
141
    CronSchedule cronSchedule = workflowMethod.getAnnotation(CronSchedule.class);
1✔
142
    WorkflowOptions mergedOptions = WorkflowOptions.merge(methodRetry, cronSchedule, options);
1✔
143
    String workflowType = workflowMethodMetadata.get().getName();
1✔
144
    WorkflowStub stub =
1✔
145
        new WorkflowStubImpl(
146
            clientOptions, workflowClientCallsInvoker, workflowType, mergedOptions);
147
    for (WorkflowClientInterceptor i : clientOptions.getInterceptors()) {
1✔
148
      stub = i.newUntypedWorkflowStub(workflowType, mergedOptions, stub);
1✔
149
    }
150
    this.untyped = stub;
1✔
151
  }
1✔
152

153
  @Override
154
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
155
    try {
156
      if (method.equals(Object.class.getMethod("toString"))) {
1✔
157
        // TODO: workflow info
158
        return "WorkflowInvocationHandler";
×
159
      }
160
    } catch (NoSuchMethodException e) {
×
161
      throw new Error("unexpected", e);
×
162
    }
1✔
163
    // Implement StubMarker
164
    if (method.getName().equals(StubMarker.GET_UNTYPED_STUB_METHOD)) {
1✔
165
      return untyped;
1✔
166
    }
167
    if (!method.getDeclaringClass().isInterface()) {
1✔
168
      throw new IllegalArgumentException(
×
169
          "Interface type is expected: " + method.getDeclaringClass());
×
170
    }
171
    SpecificInvocationHandler handler = invocationContext.get();
1✔
172
    if (handler == null) {
1✔
173
      handler = new SyncWorkflowInvocationHandler();
1✔
174
    }
175
    handler.invoke(this.workflowMetadata, untyped, method, args);
1✔
176
    if (handler.getInvocationType() == InvocationType.SYNC) {
1✔
177
      return handler.getResult(method.getReturnType());
1✔
178
    }
179
    return Defaults.defaultValue(method.getReturnType());
1✔
180
  }
181

182
  private static void startWorkflow(WorkflowStub untyped, Object[] args) {
183
    Optional<WorkflowOptions> options = untyped.getOptions();
1✔
184
    if (untyped.getExecution() == null
1✔
185
        || (options.isPresent()
1✔
186
            && options.get().getWorkflowIdReusePolicy()
1✔
187
                == WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE)) {
188
      try {
189
        untyped.start(args);
1✔
190
      } catch (WorkflowExecutionAlreadyStarted e) {
1✔
191
        // We do allow duplicated calls if policy is not AllowDuplicate. Semantic is to wait for
192
        // result.
193
        if (options.isPresent()
1✔
194
            && options.get().getWorkflowIdReusePolicy()
1✔
195
                == WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE) {
196
          throw e;
×
197
        }
198
      }
1✔
199
    }
200
  }
1✔
201

202
  static void checkAnnotations(
203
      Method method,
204
      WorkflowMethod workflowMethod,
205
      QueryMethod queryMethod,
206
      SignalMethod signalMethod) {
207
    int count =
208
        (workflowMethod == null ? 0 : 1)
×
209
            + (queryMethod == null ? 0 : 1)
×
210
            + (signalMethod == null ? 0 : 1);
×
211
    if (count > 1) {
×
212
      throw new IllegalArgumentException(
×
213
          method
214
              + " must contain at most one annotation "
215
              + "from @WorkflowMethod, @QueryMethod or @SignalMethod");
216
    }
217
  }
×
218

219
  private static class StartWorkflowInvocationHandler implements SpecificInvocationHandler {
220

221
    private Object result;
222

223
    @Override
224
    public InvocationType getInvocationType() {
225
      return InvocationType.START;
1✔
226
    }
227

228
    @Override
229
    public void invoke(
230
        POJOWorkflowInterfaceMetadata workflowMetadata,
231
        WorkflowStub untyped,
232
        Method method,
233
        Object[] args) {
234
      WorkflowMethod workflowMethod = method.getAnnotation(WorkflowMethod.class);
1✔
235
      if (workflowMethod == null) {
1✔
236
        throw new IllegalArgumentException(
×
237
            "WorkflowClient.start can be called only on a method annotated with @WorkflowMethod");
238
      }
239
      result = untyped.start(args);
1✔
240
    }
1✔
241

242
    @Override
243
    @SuppressWarnings("unchecked")
244
    public <R> R getResult(Class<R> resultClass) {
245
      return (R) result;
1✔
246
    }
247
  }
248

249
  private static class SyncWorkflowInvocationHandler implements SpecificInvocationHandler {
250

251
    private Object result;
252

253
    @Override
254
    public InvocationType getInvocationType() {
255
      return InvocationType.SYNC;
1✔
256
    }
257

258
    @Override
259
    public void invoke(
260
        POJOWorkflowInterfaceMetadata workflowMetadata,
261
        WorkflowStub untyped,
262
        Method method,
263
        Object[] args) {
264
      POJOWorkflowMethodMetadata methodMetadata = workflowMetadata.getMethodMetadata(method);
1✔
265
      WorkflowMethodType type = methodMetadata.getType();
1✔
266
      if (type == WorkflowMethodType.WORKFLOW) {
1✔
267
        result = startWorkflow(untyped, method, args);
1✔
268
      } else if (type == WorkflowMethodType.QUERY) {
1✔
269
        result = queryWorkflow(methodMetadata, untyped, method, args);
1✔
270
      } else if (type == WorkflowMethodType.SIGNAL) {
1✔
271
        signalWorkflow(methodMetadata, untyped, method, args);
1✔
272
        result = null;
1✔
273
      } else if (type == WorkflowMethodType.UPDATE) {
×
274
        result = updateWorkflow(methodMetadata, untyped, method, args);
×
275
      } else {
276
        throw new IllegalArgumentException(
×
277
            method + " is not annotated with @WorkflowMethod, @QueryMethod, @UpdateMethod");
278
      }
279
    }
1✔
280

281
    @Override
282
    @SuppressWarnings("unchecked")
283
    public <R> R getResult(Class<R> resultClass) {
284
      return (R) result;
1✔
285
    }
286

287
    private void signalWorkflow(
288
        POJOWorkflowMethodMetadata methodMetadata,
289
        WorkflowStub untyped,
290
        Method method,
291
        Object[] args) {
292
      if (method.getReturnType() != Void.TYPE) {
1✔
293
        throw new IllegalArgumentException("Signal method must have void return type: " + method);
×
294
      }
295
      String signalName = methodMetadata.getName();
1✔
296
      untyped.signal(signalName, args);
1✔
297
    }
1✔
298

299
    private Object queryWorkflow(
300
        POJOWorkflowMethodMetadata methodMetadata,
301
        WorkflowStub untyped,
302
        Method method,
303
        Object[] args) {
304
      if (method.getReturnType() == Void.TYPE) {
1✔
305
        throw new IllegalArgumentException("Query method cannot have void return type: " + method);
×
306
      }
307
      String queryType = methodMetadata.getName();
1✔
308
      return untyped.query(queryType, method.getReturnType(), method.getGenericReturnType(), args);
1✔
309
    }
310

311
    private Object updateWorkflow(
312
        POJOWorkflowMethodMetadata methodMetadata,
313
        WorkflowStub untyped,
314
        Method method,
315
        Object[] args) {
316
      String updateType = methodMetadata.getName();
×
317
      return untyped.update(updateType, method.getReturnType(), args);
×
318
    }
319

320
    @SuppressWarnings("FutureReturnValueIgnored")
321
    private Object startWorkflow(WorkflowStub untyped, Method method, Object[] args) {
322
      WorkflowInvocationHandler.startWorkflow(untyped, args);
1✔
323
      return untyped.getResult(method.getReturnType(), method.getGenericReturnType());
1✔
324
    }
325
  }
326

327
  private static class ExecuteWorkflowInvocationHandler implements SpecificInvocationHandler {
328

329
    private Object result;
330

331
    @Override
332
    public InvocationType getInvocationType() {
333
      return InvocationType.EXECUTE;
1✔
334
    }
335

336
    @Override
337
    public void invoke(
338
        POJOWorkflowInterfaceMetadata workflowMetadata,
339
        WorkflowStub untyped,
340
        Method method,
341
        Object[] args) {
342
      WorkflowMethod workflowMethod = method.getAnnotation(WorkflowMethod.class);
1✔
343
      if (workflowMethod == null) {
1✔
344
        throw new IllegalArgumentException(
×
345
            "WorkflowClient.execute can be called only on a method annotated with @WorkflowMethod");
346
      }
347
      WorkflowInvocationHandler.startWorkflow(untyped, args);
1✔
348
      result = untyped.getResultAsync(method.getReturnType(), method.getGenericReturnType());
1✔
349
    }
1✔
350

351
    @Override
352
    @SuppressWarnings("unchecked")
353
    public <R> R getResult(Class<R> resultClass) {
354
      return (R) result;
1✔
355
    }
356
  }
357

358
  private static class SignalWithStartWorkflowInvocationHandler
359
      implements SpecificInvocationHandler {
360

361
    private final SignalWithStartBatchRequest batch;
362

363
    public SignalWithStartWorkflowInvocationHandler(SignalWithStartBatchRequest batch) {
1✔
364
      this.batch = batch;
1✔
365
    }
1✔
366

367
    @Override
368
    public InvocationType getInvocationType() {
369
      return InvocationType.SIGNAL_WITH_START;
1✔
370
    }
371

372
    @Override
373
    public void invoke(
374
        POJOWorkflowInterfaceMetadata workflowMetadata,
375
        WorkflowStub untyped,
376
        Method method,
377
        Object[] args) {
378
      POJOWorkflowMethodMetadata methodMetadata = workflowMetadata.getMethodMetadata(method);
1✔
379
      switch (methodMetadata.getType()) {
1✔
380
        case QUERY:
381
          throw new IllegalArgumentException(
×
382
              "SignalWithStart batch doesn't accept methods annotated with @QueryMethod");
383
        case WORKFLOW:
384
          batch.start(untyped, args);
1✔
385
          break;
1✔
386
        case SIGNAL:
387
          batch.signal(untyped, methodMetadata.getName(), args);
1✔
388
          break;
389
      }
390
    }
1✔
391

392
    @Override
393
    public <R> R getResult(Class<R> resultClass) {
394
      throw new IllegalStateException("No result is expected");
×
395
    }
396
  }
397
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc