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

temporalio / sdk-java / #181

pending completion
#181

push

github-actions

web-flow
Properly wrap exceptions from schedule client (#1827)

Wrap schedule exception

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

18557 of 23894 relevant lines covered (77.66%)

0.78 hits per line

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

85.71
/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.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.internal.sync;
22

23
import static io.temporal.internal.sync.AsyncInternal.AsyncMarker;
24
import static io.temporal.internal.sync.DeterministicRunnerImpl.currentThreadInternal;
25

26
import com.google.common.base.Joiner;
27
import com.google.common.base.MoreObjects;
28
import com.google.common.base.Preconditions;
29
import com.uber.m3.tally.Scope;
30
import io.temporal.activity.ActivityOptions;
31
import io.temporal.activity.LocalActivityOptions;
32
import io.temporal.api.common.v1.Payload;
33
import io.temporal.api.common.v1.SearchAttributes;
34
import io.temporal.api.common.v1.WorkflowExecution;
35
import io.temporal.common.RetryOptions;
36
import io.temporal.common.SearchAttributeUpdate;
37
import io.temporal.common.converter.DataConverter;
38
import io.temporal.common.interceptors.Header;
39
import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor;
40
import io.temporal.common.metadata.POJOWorkflowImplMetadata;
41
import io.temporal.common.metadata.POJOWorkflowInterfaceMetadata;
42
import io.temporal.common.metadata.POJOWorkflowMethodMetadata;
43
import io.temporal.internal.WorkflowThreadMarker;
44
import io.temporal.internal.common.ActivityOptionUtils;
45
import io.temporal.internal.common.NonIdempotentHandle;
46
import io.temporal.internal.common.SearchAttributesUtil;
47
import io.temporal.internal.logging.ReplayAwareLogger;
48
import io.temporal.serviceclient.CheckedExceptionWrapper;
49
import io.temporal.workflow.*;
50
import io.temporal.workflow.Functions.Func;
51
import java.lang.reflect.*;
52
import java.time.Duration;
53
import java.util.*;
54
import java.util.function.BiPredicate;
55
import java.util.function.Supplier;
56
import javax.annotation.Nonnull;
57
import javax.annotation.Nullable;
58
import org.slf4j.Logger;
59
import org.slf4j.LoggerFactory;
60

61
/**
62
 * Never reference directly. It is public only because Java doesn't have internal package support.
63
 */
64
public final class WorkflowInternal {
65
  public static final int DEFAULT_VERSION = -1;
66

67
  public static @Nonnull WorkflowThread newWorkflowMethodThread(Runnable runnable, String name) {
68
    Object workflowThread =
69
        currentThreadInternal()
1✔
70
            .getWorkflowContext()
1✔
71
            .getWorkflowInboundInterceptor()
1✔
72
            .newWorkflowMethodThread(runnable, name);
1✔
73
    Preconditions.checkState(
1✔
74
        workflowThread != null,
75
        "[BUG] One of the custom interceptors illegally overrode newWorkflowMethodThread result to null. "
76
            + "Check WorkflowInboundCallsInterceptor#newWorkflowMethodThread contract.");
77
    Preconditions.checkState(
1✔
78
        workflowThread instanceof WorkflowThread,
79
        "[BUG] One of the custom interceptors illegally overrode newWorkflowMethodThread result. "
80
            + "Check WorkflowInboundCallsInterceptor#newWorkflowMethodThread contract. "
81
            + "Illegal object returned from the interceptors chain: %s",
82
        workflowThread);
83
    return (WorkflowThread) workflowThread;
1✔
84
  }
85

86
  public static Promise<Void> newTimer(Duration duration) {
87
    assertNotReadOnly("schedule timer");
1✔
88
    return getWorkflowOutboundInterceptor().newTimer(duration);
1✔
89
  }
90

91
  /**
92
   * @param capacity the maximum size of the queue
93
   * @return new instance of {@link WorkflowQueue}
94
   * @deprecated this method created a deprecated implementation of the queue that has some methods
95
   *     implemented incorrectly. Please use {@link #newWorkflowQueue(int)} instead.
96
   */
97
  @Deprecated
98
  public static <E> WorkflowQueue<E> newQueue(int capacity) {
99
    return new WorkflowQueueDeprecatedImpl<>(capacity);
1✔
100
  }
101

102
  /**
103
   * Creates a {@link WorkflowQueue} implementation that can be used from workflow code.
104
   *
105
   * @param capacity the maximum size of the queue
106
   * @return new instance of {@link WorkflowQueue}
107
   */
108
  public static <E> WorkflowQueue<E> newWorkflowQueue(int capacity) {
109
    return new WorkflowQueueImpl<>(capacity);
1✔
110
  }
111

112
  public static <E> CompletablePromise<E> newCompletablePromise() {
113
    return new CompletablePromiseImpl<>();
1✔
114
  }
115

116
  public static <E> Promise<E> newPromise(E value) {
117
    CompletablePromise<E> result = Workflow.newPromise();
1✔
118
    result.complete(value);
1✔
119
    return result;
1✔
120
  }
121

122
  public static <E> Promise<E> newFailedPromise(Exception failure) {
123
    CompletablePromise<E> result = new CompletablePromiseImpl<>();
1✔
124
    result.completeExceptionally(CheckedExceptionWrapper.wrap(failure));
1✔
125
    return result;
1✔
126
  }
127

128
  /**
129
   * Register query or queries implementation object. There is no need to register top level
130
   * workflow implementation object as it is done implicitly. Only methods annotated with @{@link
131
   * QueryMethod} are registered. TODO(quinn) LIES!
132
   */
133
  public static void registerListener(Object implementation) {
134
    if (implementation instanceof DynamicSignalHandler) {
1✔
135
      getWorkflowOutboundInterceptor()
1✔
136
          .registerDynamicSignalHandler(
1✔
137
              new WorkflowOutboundCallsInterceptor.RegisterDynamicSignalHandlerInput(
138
                  (DynamicSignalHandler) implementation));
139
      return;
1✔
140
    }
141
    if (implementation instanceof DynamicQueryHandler) {
1✔
142
      getWorkflowOutboundInterceptor()
1✔
143
          .registerDynamicQueryHandler(
1✔
144
              new WorkflowOutboundCallsInterceptor.RegisterDynamicQueryHandlerInput(
145
                  (DynamicQueryHandler) implementation));
146
      return;
1✔
147
    }
148
    if (implementation instanceof DynamicUpdateHandler) {
1✔
149
      getWorkflowOutboundInterceptor()
×
150
          .registerDynamicUpdateHandler(
×
151
              new WorkflowOutboundCallsInterceptor.RegisterDynamicUpdateHandlerInput(
152
                  (DynamicUpdateHandler) implementation));
153
      return;
×
154
    }
155
    Class<?> cls = implementation.getClass();
1✔
156
    POJOWorkflowImplMetadata workflowMetadata = POJOWorkflowImplMetadata.newListenerInstance(cls);
1✔
157
    for (POJOWorkflowMethodMetadata methodMetadata : workflowMetadata.getQueryMethods()) {
1✔
158
      Method method = methodMetadata.getWorkflowMethod();
1✔
159
      getWorkflowOutboundInterceptor()
1✔
160
          .registerQuery(
1✔
161
              new WorkflowOutboundCallsInterceptor.RegisterQueryInput(
162
                  methodMetadata.getName(),
1✔
163
                  method.getParameterTypes(),
1✔
164
                  method.getGenericParameterTypes(),
1✔
165
                  (args) -> {
166
                    try {
167
                      return method.invoke(implementation, args);
1✔
168
                    } catch (Throwable e) {
×
169
                      throw CheckedExceptionWrapper.wrap(e);
×
170
                    }
171
                  }));
172
    }
1✔
173
    List<WorkflowOutboundCallsInterceptor.SignalRegistrationRequest> requests = new ArrayList<>();
1✔
174
    for (POJOWorkflowMethodMetadata methodMetadata : workflowMetadata.getSignalMethods()) {
1✔
175
      Method method = methodMetadata.getWorkflowMethod();
1✔
176
      requests.add(
1✔
177
          new WorkflowOutboundCallsInterceptor.SignalRegistrationRequest(
178
              methodMetadata.getName(),
1✔
179
              method.getParameterTypes(),
1✔
180
              method.getGenericParameterTypes(),
1✔
181
              (args) -> {
182
                try {
183
                  method.invoke(implementation, args);
1✔
184
                } catch (Throwable e) {
1✔
185
                  throw CheckedExceptionWrapper.wrap(e);
1✔
186
                }
1✔
187
              }));
1✔
188
    }
1✔
189
    if (!requests.isEmpty()) {
1✔
190
      getWorkflowOutboundInterceptor()
1✔
191
          .registerSignalHandlers(
1✔
192
              new WorkflowOutboundCallsInterceptor.RegisterSignalHandlersInput(requests));
193
    }
194

195
    // Get all validators and lazily assign them to update handlers as we see them.
196
    Map<String, POJOWorkflowMethodMetadata> validators =
1✔
197
        new HashMap<>(workflowMetadata.getUpdateValidatorMethods().size());
1✔
198
    for (POJOWorkflowMethodMetadata methodMetadata : workflowMetadata.getUpdateValidatorMethods()) {
1✔
199
      Method method = methodMetadata.getWorkflowMethod();
1✔
200
      UpdateValidatorMethod updateValidatorMethod =
1✔
201
          method.getAnnotation(UpdateValidatorMethod.class);
1✔
202
      if (validators.containsKey(updateValidatorMethod.updateName())) {
1✔
203
        throw new IllegalArgumentException(
×
204
            "Duplicate validator for update handle " + updateValidatorMethod.updateName());
×
205
      }
206
      validators.put(updateValidatorMethod.updateName(), methodMetadata);
1✔
207
    }
1✔
208

209
    List<WorkflowOutboundCallsInterceptor.UpdateRegistrationRequest> updateRequests =
1✔
210
        new ArrayList<>();
211
    for (POJOWorkflowMethodMetadata methodMetadata : workflowMetadata.getUpdateMethods()) {
1✔
212
      Method method = methodMetadata.getWorkflowMethod();
1✔
213
      UpdateMethod updateMethod = method.getAnnotation(UpdateMethod.class);
1✔
214
      String updateMethodName = updateMethod.name();
1✔
215
      if (updateMethodName.isEmpty()) {
1✔
216
        updateMethodName = method.getName();
1✔
217
      }
218
      // Check if any validators claim they are the validator for this update
219
      POJOWorkflowMethodMetadata validatorMethodMetadata = validators.remove(updateMethodName);
1✔
220
      Method validatorMethod;
221
      if (validatorMethodMetadata != null) {
1✔
222
        validatorMethod = validatorMethodMetadata.getWorkflowMethod();
1✔
223
        if (!Arrays.equals(validatorMethod.getParameterTypes(), method.getParameterTypes())) {
1✔
224
          throw new IllegalArgumentException(
×
225
              "Validator for: "
226
                  + updateMethodName
227
                  + " type parameters do not match the update handle");
228
        }
229
      } else {
230
        validatorMethod = null;
1✔
231
      }
232
      updateRequests.add(
1✔
233
          new WorkflowOutboundCallsInterceptor.UpdateRegistrationRequest(
234
              methodMetadata.getName(),
1✔
235
              method.getParameterTypes(),
1✔
236
              method.getGenericParameterTypes(),
1✔
237
              (args) -> {
238
                try {
239
                  if (validatorMethod != null) {
1✔
240
                    validatorMethod.invoke(implementation, args);
1✔
241
                  }
242
                } catch (Throwable e) {
1✔
243
                  throw CheckedExceptionWrapper.wrap(e);
1✔
244
                }
1✔
245
              },
1✔
246
              (args) -> {
247
                try {
248
                  return method.invoke(implementation, args);
1✔
249
                } catch (Throwable e) {
1✔
250
                  throw CheckedExceptionWrapper.wrap(e);
1✔
251
                }
252
              }));
253
    }
1✔
254
    if (!updateRequests.isEmpty()) {
1✔
255
      getWorkflowOutboundInterceptor()
1✔
256
          .registerUpdateHandlers(
1✔
257
              new WorkflowOutboundCallsInterceptor.RegisterUpdateHandlersInput(updateRequests));
258
    }
259
    if (!validators.isEmpty()) {
1✔
260
      throw new IllegalArgumentException(
×
261
          "Missing update methods for update validator(s): "
262
              + Joiner.on(", ").join(validators.keySet()));
×
263
    }
264
  }
1✔
265

266
  /** Should be used to get current time instead of {@link System#currentTimeMillis()} */
267
  public static long currentTimeMillis() {
268
    return getWorkflowOutboundInterceptor().currentTimeMillis();
1✔
269
  }
270

271
  public static void setDefaultActivityOptions(ActivityOptions activityOptions) {
272
    getRootWorkflowContext().setDefaultActivityOptions(activityOptions);
1✔
273
  }
1✔
274

275
  public static void applyActivityOptions(Map<String, ActivityOptions> activityTypeToOptions) {
276
    getRootWorkflowContext().applyActivityOptions(activityTypeToOptions);
1✔
277
  }
1✔
278

279
  public static void setDefaultLocalActivityOptions(LocalActivityOptions localActivityOptions) {
280
    getRootWorkflowContext().setDefaultLocalActivityOptions(localActivityOptions);
1✔
281
  }
1✔
282

283
  public static void applyLocalActivityOptions(
284
      Map<String, LocalActivityOptions> activityTypeToOptions) {
285
    getRootWorkflowContext().applyLocalActivityOptions(activityTypeToOptions);
1✔
286
  }
1✔
287

288
  /**
289
   * Creates client stub to activities that implement given interface.
290
   *
291
   * @param activityInterface interface type implemented by activities
292
   * @param options options that together with the properties of {@link
293
   *     io.temporal.activity.ActivityMethod} specify the activity invocation parameters
294
   * @param activityMethodOptions activity method-specific invocation parameters
295
   */
296
  public static <T> T newActivityStub(
297
      Class<T> activityInterface,
298
      ActivityOptions options,
299
      Map<String, ActivityOptions> activityMethodOptions) {
300
    // Merge the activity options we may have received from the workflow with the options we may
301
    // have received in WorkflowImplementationOptions.
302
    SyncWorkflowContext context = getRootWorkflowContext();
1✔
303
    options = (options == null) ? context.getDefaultActivityOptions() : options;
1✔
304

305
    Map<String, ActivityOptions> mergedActivityOptionsMap;
306
    @Nonnull Map<String, ActivityOptions> predefinedActivityOptions = context.getActivityOptions();
1✔
307
    if (activityMethodOptions != null
1✔
308
        && !activityMethodOptions.isEmpty()
×
309
        && predefinedActivityOptions.isEmpty()) {
×
310
      // we need to merge only in this case
311
      mergedActivityOptionsMap = new HashMap<>(predefinedActivityOptions);
×
312
      ActivityOptionUtils.mergePredefinedActivityOptions(
×
313
          mergedActivityOptionsMap, activityMethodOptions);
314
    } else {
315
      mergedActivityOptionsMap =
1✔
316
          MoreObjects.firstNonNull(
1✔
317
              activityMethodOptions,
318
              MoreObjects.firstNonNull(predefinedActivityOptions, Collections.emptyMap()));
1✔
319
    }
320

321
    InvocationHandler invocationHandler =
1✔
322
        ActivityInvocationHandler.newInstance(
1✔
323
            activityInterface,
324
            options,
325
            mergedActivityOptionsMap,
326
            context.getWorkflowOutboundInterceptor(),
1✔
327
            () -> assertNotReadOnly("schedule activity"));
1✔
328
    return ActivityInvocationHandlerBase.newProxy(activityInterface, invocationHandler);
1✔
329
  }
330

331
  /**
332
   * Creates client stub to local activities that implement given interface.
333
   *
334
   * @param activityInterface interface type implemented by activities
335
   * @param options options that together with the properties of {@link
336
   *     io.temporal.activity.ActivityMethod} specify the activity invocation parameters
337
   * @param activityMethodOptions activity method-specific invocation parameters
338
   */
339
  public static <T> T newLocalActivityStub(
340
      Class<T> activityInterface,
341
      LocalActivityOptions options,
342
      @Nullable Map<String, LocalActivityOptions> activityMethodOptions) {
343
    // Merge the activity options we may have received from the workflow with the options we may
344
    // have received in WorkflowImplementationOptions.
345
    SyncWorkflowContext context = getRootWorkflowContext();
1✔
346
    options = (options == null) ? context.getDefaultLocalActivityOptions() : options;
1✔
347

348
    Map<String, LocalActivityOptions> mergedLocalActivityOptionsMap;
349
    @Nonnull
350
    Map<String, LocalActivityOptions> predefinedLocalActivityOptions =
1✔
351
        context.getLocalActivityOptions();
1✔
352
    if (activityMethodOptions != null
1✔
353
        && !activityMethodOptions.isEmpty()
×
354
        && predefinedLocalActivityOptions.isEmpty()) {
×
355
      // we need to merge only in this case
356
      mergedLocalActivityOptionsMap = new HashMap<>(predefinedLocalActivityOptions);
×
357
      ActivityOptionUtils.mergePredefinedLocalActivityOptions(
×
358
          mergedLocalActivityOptionsMap, activityMethodOptions);
359
    } else {
360
      mergedLocalActivityOptionsMap =
1✔
361
          MoreObjects.firstNonNull(
1✔
362
              activityMethodOptions,
363
              MoreObjects.firstNonNull(predefinedLocalActivityOptions, Collections.emptyMap()));
1✔
364
    }
365

366
    InvocationHandler invocationHandler =
1✔
367
        LocalActivityInvocationHandler.newInstance(
1✔
368
            activityInterface,
369
            options,
370
            mergedLocalActivityOptionsMap,
371
            WorkflowInternal.getWorkflowOutboundInterceptor(),
1✔
372
            () -> assertNotReadOnly("schedule local activity"));
1✔
373
    return ActivityInvocationHandlerBase.newProxy(activityInterface, invocationHandler);
1✔
374
  }
375

376
  public static ActivityStub newUntypedActivityStub(ActivityOptions options) {
377
    return ActivityStubImpl.newInstance(
1✔
378
        options, getWorkflowOutboundInterceptor(), () -> assertNotReadOnly("schedule activity"));
1✔
379
  }
380

381
  public static ActivityStub newUntypedLocalActivityStub(LocalActivityOptions options) {
382
    return LocalActivityStubImpl.newInstance(
1✔
383
        options,
384
        getWorkflowOutboundInterceptor(),
1✔
385
        () -> assertNotReadOnly("schedule local activity"));
1✔
386
  }
387

388
  @SuppressWarnings("unchecked")
389
  public static <T> T newChildWorkflowStub(
390
      Class<T> workflowInterface, ChildWorkflowOptions options) {
391
    return (T)
1✔
392
        Proxy.newProxyInstance(
1✔
393
            workflowInterface.getClassLoader(),
1✔
394
            new Class<?>[] {workflowInterface, StubMarker.class, AsyncMarker.class},
395
            new ChildWorkflowInvocationHandler(
396
                workflowInterface,
397
                options,
398
                getWorkflowOutboundInterceptor(),
1✔
399
                WorkflowInternal::assertNotReadOnly));
400
  }
401

402
  @SuppressWarnings("unchecked")
403
  public static <T> T newExternalWorkflowStub(
404
      Class<T> workflowInterface, WorkflowExecution execution) {
405
    return (T)
1✔
406
        Proxy.newProxyInstance(
1✔
407
            workflowInterface.getClassLoader(),
1✔
408
            new Class<?>[] {workflowInterface, StubMarker.class, AsyncMarker.class},
409
            new ExternalWorkflowInvocationHandler(
410
                workflowInterface,
411
                execution,
412
                getWorkflowOutboundInterceptor(),
1✔
413
                WorkflowInternal::assertNotReadOnly));
414
  }
415

416
  public static Promise<WorkflowExecution> getWorkflowExecution(Object workflowStub) {
417
    if (workflowStub instanceof StubMarker) {
1✔
418
      Object stub = ((StubMarker) workflowStub).__getUntypedStub();
1✔
419
      return ((ChildWorkflowStub) stub).getExecution();
1✔
420
    }
421
    throw new IllegalArgumentException(
×
422
        "Not a workflow stub created through Workflow.newChildWorkflowStub: " + workflowStub);
423
  }
424

425
  public static ChildWorkflowStub newUntypedChildWorkflowStub(
426
      String workflowType, ChildWorkflowOptions options) {
427
    return new ChildWorkflowStubImpl(
1✔
428
        workflowType,
429
        options,
430
        getWorkflowOutboundInterceptor(),
1✔
431
        WorkflowInternal::assertNotReadOnly);
432
  }
433

434
  public static ExternalWorkflowStub newUntypedExternalWorkflowStub(WorkflowExecution execution) {
435
    return new ExternalWorkflowStubImpl(
1✔
436
        execution, getWorkflowOutboundInterceptor(), WorkflowInternal::assertNotReadOnly);
1✔
437
  }
438

439
  /**
440
   * Creates client stub that can be used to continue this workflow as new.
441
   *
442
   * @param workflowInterface interface type implemented by the next generation of workflow
443
   */
444
  @SuppressWarnings("unchecked")
445
  public static <T> T newContinueAsNewStub(
446
      Class<T> workflowInterface, ContinueAsNewOptions options) {
447
    return (T)
1✔
448
        Proxy.newProxyInstance(
1✔
449
            workflowInterface.getClassLoader(),
1✔
450
            new Class<?>[] {workflowInterface},
451
            new ContinueAsNewWorkflowInvocationHandler(
452
                workflowInterface, options, getWorkflowOutboundInterceptor()));
1✔
453
  }
454

455
  /**
456
   * Execute activity by name.
457
   *
458
   * @param name name of the activity
459
   * @param resultClass activity return type
460
   * @param args list of activity arguments
461
   * @param <R> activity return type
462
   * @return activity result
463
   */
464
  public static <R> R executeActivity(
465
      String name, ActivityOptions options, Class<R> resultClass, Type resultType, Object... args) {
466
    assertNotReadOnly("schedule activity");
×
467
    Promise<R> result =
468
        getWorkflowOutboundInterceptor()
×
469
            .executeActivity(
×
470
                new WorkflowOutboundCallsInterceptor.ActivityInput<>(
471
                    name, resultClass, resultType, args, options, Header.empty()))
×
472
            .getResult();
×
473
    if (AsyncInternal.isAsync()) {
×
474
      AsyncInternal.setAsyncResult(result);
×
475
      return null; // ignored
×
476
    }
477
    return result.get();
×
478
  }
479

480
  public static void await(String reason, Supplier<Boolean> unblockCondition)
481
      throws DestroyWorkflowThreadError {
482
    assertNotReadOnly("await");
1✔
483
    getWorkflowOutboundInterceptor().await(reason, unblockCondition);
1✔
484
  }
1✔
485

486
  public static boolean await(Duration timeout, String reason, Supplier<Boolean> unblockCondition)
487
      throws DestroyWorkflowThreadError {
488
    assertNotReadOnly("await with timeout");
1✔
489
    return getWorkflowOutboundInterceptor().await(timeout, reason, unblockCondition);
1✔
490
  }
491

492
  public static <R> R sideEffect(Class<R> resultClass, Type resultType, Func<R> func) {
493
    assertNotReadOnly("side effect");
1✔
494
    return getWorkflowOutboundInterceptor().sideEffect(resultClass, resultType, func);
1✔
495
  }
496

497
  public static <R> R mutableSideEffect(
498
      String id, Class<R> resultClass, Type resultType, BiPredicate<R, R> updated, Func<R> func) {
499
    assertNotReadOnly("mutable side effect");
1✔
500
    return getWorkflowOutboundInterceptor()
1✔
501
        .mutableSideEffect(id, resultClass, resultType, updated, func);
1✔
502
  }
503

504
  public static int getVersion(String changeId, int minSupported, int maxSupported) {
505
    assertNotReadOnly("get version");
1✔
506
    return getWorkflowOutboundInterceptor().getVersion(changeId, minSupported, maxSupported);
1✔
507
  }
508

509
  public static <V> Promise<Void> promiseAllOf(Iterable<Promise<V>> promises) {
510
    return new AllOfPromise(promises);
1✔
511
  }
512

513
  public static Promise<Void> promiseAllOf(Promise<?>... promises) {
514
    return new AllOfPromise(promises);
1✔
515
  }
516

517
  public static <V> Promise<V> promiseAnyOf(Iterable<Promise<V>> promises) {
518
    return CompletablePromiseImpl.promiseAnyOf(promises);
1✔
519
  }
520

521
  public static Promise<Object> promiseAnyOf(Promise<?>... promises) {
522
    return CompletablePromiseImpl.promiseAnyOf(promises);
1✔
523
  }
524

525
  public static CancellationScope newCancellationScope(boolean detached, Runnable runnable) {
526
    return new CancellationScopeImpl(detached, runnable);
1✔
527
  }
528

529
  public static CancellationScope newCancellationScope(
530
      boolean detached, Functions.Proc1<CancellationScope> proc) {
531
    return new CancellationScopeImpl(detached, proc);
1✔
532
  }
533

534
  public static CancellationScopeImpl currentCancellationScope() {
535
    return CancellationScopeImpl.current();
1✔
536
  }
537

538
  public static RuntimeException wrap(Throwable e) {
539
    return CheckedExceptionWrapper.wrap(e);
1✔
540
  }
541

542
  public static Throwable unwrap(Throwable e) {
543
    return CheckedExceptionWrapper.unwrap(e);
1✔
544
  }
545

546
  /** Returns false if not under workflow code. */
547
  public static boolean isReplaying() {
548
    Optional<WorkflowThread> thread = DeterministicRunnerImpl.currentThreadInternalIfPresent();
1✔
549
    return thread.isPresent() && getRootWorkflowContext().isReplaying();
1✔
550
  }
551

552
  public static <T> T getMemo(String key, Class<T> valueClass, Type genericType) {
553
    Payload memo = getRootWorkflowContext().getReplayContext().getMemo(key);
1✔
554
    if (memo == null) {
1✔
555
      return null;
1✔
556
    }
557

558
    return getDataConverter().fromPayload(memo, valueClass, genericType);
1✔
559
  }
560

561
  public static <R> R retry(
562
      RetryOptions options, Optional<Duration> expiration, Functions.Func<R> fn) {
563
    assertNotReadOnly("retry");
1✔
564
    return WorkflowRetryerInternal.retry(
1✔
565
        options.toBuilder().validateBuildWithDefaults(), expiration, fn);
1✔
566
  }
567

568
  public static void continueAsNew(
569
      @Nullable String workflowType, @Nullable ContinueAsNewOptions options, Object[] args) {
570
    assertNotReadOnly("continue as new");
1✔
571
    getWorkflowOutboundInterceptor()
1✔
572
        .continueAsNew(
×
573
            new WorkflowOutboundCallsInterceptor.ContinueAsNewInput(
574
                workflowType, options, args, Header.empty()));
1✔
575
  }
×
576

577
  public static void continueAsNew(
578
      @Nullable String workflowType,
579
      @Nullable ContinueAsNewOptions options,
580
      Object[] args,
581
      WorkflowOutboundCallsInterceptor outboundCallsInterceptor) {
582
    assertNotReadOnly("continue as new");
1✔
583
    outboundCallsInterceptor.continueAsNew(
1✔
584
        new WorkflowOutboundCallsInterceptor.ContinueAsNewInput(
585
            workflowType, options, args, Header.empty()));
1✔
586
  }
×
587

588
  public static Promise<Void> cancelWorkflow(WorkflowExecution execution) {
589
    assertNotReadOnly("cancel workflow");
×
590
    return getWorkflowOutboundInterceptor()
×
591
        .cancelWorkflow(new WorkflowOutboundCallsInterceptor.CancelWorkflowInput(execution))
×
592
        .getResult();
×
593
  }
594

595
  public static void sleep(Duration duration) {
596
    assertNotReadOnly("sleep");
1✔
597
    getWorkflowOutboundInterceptor().sleep(duration);
1✔
598
  }
1✔
599

600
  public static boolean isWorkflowThread() {
601
    return WorkflowThreadMarker.isWorkflowThread();
1✔
602
  }
603

604
  public static <T> T deadlockDetectorOff(Functions.Func<T> func) {
605
    if (isWorkflowThread()) {
1✔
606
      try (NonIdempotentHandle ignored = getWorkflowThread().lockDeadlockDetector()) {
1✔
607
        return func.apply();
1✔
608
      }
609
    } else {
610
      return func.apply();
1✔
611
    }
612
  }
613

614
  public static WorkflowInfo getWorkflowInfo() {
615
    return new WorkflowInfoImpl(getRootWorkflowContext().getReplayContext());
1✔
616
  }
617

618
  public static Scope getMetricsScope() {
619
    return getRootWorkflowContext().getMetricsScope();
1✔
620
  }
621

622
  private static boolean isLoggingEnabledInReplay() {
623
    return getRootWorkflowContext().isLoggingEnabledInReplay();
×
624
  }
625

626
  public static UUID randomUUID() {
627
    assertNotReadOnly("random UUID");
1✔
628
    return getRootWorkflowContext().randomUUID();
1✔
629
  }
630

631
  public static Random newRandom() {
632
    assertNotReadOnly("random");
1✔
633
    return getRootWorkflowContext().newRandom();
1✔
634
  }
635

636
  public static Logger getLogger(Class<?> clazz) {
637
    Logger logger = LoggerFactory.getLogger(clazz);
1✔
638
    return new ReplayAwareLogger(
1✔
639
        logger, WorkflowInternal::isReplaying, WorkflowInternal::isLoggingEnabledInReplay);
640
  }
641

642
  public static Logger getLogger(String name) {
643
    Logger logger = LoggerFactory.getLogger(name);
×
644
    return new ReplayAwareLogger(
×
645
        logger, WorkflowInternal::isReplaying, WorkflowInternal::isLoggingEnabledInReplay);
646
  }
647

648
  public static <R> R getLastCompletionResult(Class<R> resultClass, Type resultType) {
649
    return getRootWorkflowContext().getLastCompletionResult(resultClass, resultType);
1✔
650
  }
651

652
  @Nullable
653
  public static <T> T getSearchAttribute(String name) {
654
    List<T> list = getSearchAttributeValues(name);
1✔
655
    if (list == null) {
1✔
656
      return null;
1✔
657
    }
658
    Preconditions.checkState(list.size() > 0);
1✔
659
    Preconditions.checkState(
1✔
660
        list.size() == 1,
1✔
661
        "search attribute with name '%s' contains a list '%s' of values instead of a single value",
662
        name,
663
        list);
664
    return list.get(0);
1✔
665
  }
666

667
  @Nullable
668
  public static <T> List<T> getSearchAttributeValues(String name) {
669
    SearchAttributes searchAttributes =
670
        getRootWorkflowContext().getReplayContext().getSearchAttributes();
1✔
671
    if (searchAttributes == null) {
1✔
672
      return null;
×
673
    }
674
    List<T> decoded = SearchAttributesUtil.decode(searchAttributes, name);
1✔
675
    return decoded != null ? Collections.unmodifiableList(decoded) : null;
1✔
676
  }
677

678
  @Nonnull
679
  public static Map<String, List<?>> getSearchAttributes() {
680
    SearchAttributes searchAttributes =
681
        getRootWorkflowContext().getReplayContext().getSearchAttributes();
1✔
682
    if (searchAttributes == null) {
1✔
683
      return Collections.emptyMap();
×
684
    }
685
    return Collections.unmodifiableMap(SearchAttributesUtil.decode(searchAttributes));
1✔
686
  }
687

688
  @Nonnull
689
  public static io.temporal.common.SearchAttributes getTypedSearchAttributes() {
690
    SearchAttributes searchAttributes =
691
        getRootWorkflowContext().getReplayContext().getSearchAttributes();
1✔
692
    return SearchAttributesUtil.decodeTyped(searchAttributes);
1✔
693
  }
694

695
  public static void upsertSearchAttributes(Map<String, ?> searchAttributes) {
696
    assertNotReadOnly("upset search attribute");
1✔
697
    getWorkflowOutboundInterceptor().upsertSearchAttributes(searchAttributes);
1✔
698
  }
1✔
699

700
  public static void upsertTypedSearchAttributes(
701
      SearchAttributeUpdate<?>... searchAttributeUpdates) {
702
    assertNotReadOnly("upset search attribute");
1✔
703
    getWorkflowOutboundInterceptor().upsertTypedSearchAttributes(searchAttributeUpdates);
1✔
704
  }
1✔
705

706
  public static DataConverter getDataConverter() {
707
    return getRootWorkflowContext().getDataConverter();
1✔
708
  }
709

710
  /**
711
   * Name of the workflow type the interface defines. It is either the interface short name * or
712
   * value of {@link WorkflowMethod#name()} parameter.
713
   *
714
   * @param workflowInterfaceClass interface annotated with @WorkflowInterface
715
   */
716
  public static String getWorkflowType(Class<?> workflowInterfaceClass) {
717
    POJOWorkflowInterfaceMetadata metadata =
1✔
718
        POJOWorkflowInterfaceMetadata.newInstance(workflowInterfaceClass);
1✔
719
    return metadata.getWorkflowType().get();
1✔
720
  }
721

722
  public static Optional<Exception> getPreviousRunFailure() {
723
    return Optional.ofNullable(getRootWorkflowContext().getReplayContext().getPreviousRunFailure())
1✔
724
        // Temporal Failure Values are additional user payload and serialized using user data
725
        // converter
726
        .map(f -> getDataConverter().failureToException(f));
1✔
727
  }
728

729
  private static WorkflowOutboundCallsInterceptor getWorkflowOutboundInterceptor() {
730
    return getRootWorkflowContext().getWorkflowOutboundInterceptor();
1✔
731
  }
732

733
  static SyncWorkflowContext getRootWorkflowContext() {
734
    return DeterministicRunnerImpl.currentThreadInternal().getWorkflowContext();
1✔
735
  }
736

737
  static boolean isReadOnly() {
738
    return getRootWorkflowContext().isReadOnly();
1✔
739
  }
740

741
  static void assertNotReadOnly(String action) {
742
    if (isReadOnly()) {
1✔
743
      throw new IllegalStateException("While in read-only function, action attempted:" + action);
1✔
744
    }
745
  }
1✔
746

747
  private static WorkflowThread getWorkflowThread() {
748
    return DeterministicRunnerImpl.currentThreadInternal();
1✔
749
  }
750

751
  /** Prohibit instantiation */
752
  private WorkflowInternal() {}
753
}
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

© 2025 Coveralls, Inc