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

temporalio / sdk-java / #157

pending completion
#157

push

github-actions

web-flow
Provide SerializationContext for PayloadConverter and PayloadCodec (#1695)

Issue #1694

497 of 497 new or added lines in 32 files covered. (100.0%)

16942 of 20806 relevant lines covered (81.43%)

0.81 hits per line

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

81.25
/temporal-sdk/src/main/java/io/temporal/failure/DefaultFailureConverter.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.failure;
22

23
import com.google.common.base.Preconditions;
24
import com.google.common.base.Strings;
25
import com.google.common.collect.ImmutableSet;
26
import io.temporal.api.common.v1.ActivityType;
27
import io.temporal.api.common.v1.Payloads;
28
import io.temporal.api.common.v1.WorkflowType;
29
import io.temporal.api.failure.v1.ActivityFailureInfo;
30
import io.temporal.api.failure.v1.ApplicationFailureInfo;
31
import io.temporal.api.failure.v1.CanceledFailureInfo;
32
import io.temporal.api.failure.v1.ChildWorkflowExecutionFailureInfo;
33
import io.temporal.api.failure.v1.Failure;
34
import io.temporal.api.failure.v1.ResetWorkflowFailureInfo;
35
import io.temporal.api.failure.v1.ServerFailureInfo;
36
import io.temporal.api.failure.v1.TerminatedFailureInfo;
37
import io.temporal.api.failure.v1.TimeoutFailureInfo;
38
import io.temporal.client.ActivityCanceledException;
39
import io.temporal.common.converter.DataConverter;
40
import io.temporal.common.converter.EncodedValues;
41
import io.temporal.common.converter.FailureConverter;
42
import io.temporal.serviceclient.CheckedExceptionWrapper;
43
import java.io.PrintWriter;
44
import java.io.StringWriter;
45
import java.util.Optional;
46
import java.util.regex.Matcher;
47
import java.util.regex.Pattern;
48
import javax.annotation.Nonnull;
49
import org.slf4j.Logger;
50
import org.slf4j.LoggerFactory;
51

52
/**
53
 * A {@link FailureConverter} that implements the default cross-language-compatible conversion
54
 * algorithm.
55
 */
56
public final class DefaultFailureConverter implements FailureConverter {
1✔
57

58
  private static final Logger log = LoggerFactory.getLogger(DefaultFailureConverter.class);
1✔
59

60
  private static final String JAVA_SDK = "JavaSDK";
61

62
  /**
63
   * Stop emitting stack trace after this line. Makes serialized stack traces more readable and
64
   * compact as it omits most of framework level code.
65
   */
66
  private static final ImmutableSet<String> CUTOFF_METHOD_NAMES =
1✔
67
      ImmutableSet.of(
1✔
68
          "io.temporal.internal.worker.POJOActivityImplementationFactory$POJOActivityImplementation.execute",
69
          "io.temporal.internal.sync.POJOWorkflowTaskHandler$POJOWorkflowImplementation.execute");
70

71
  /** Used to parse a stack trace line. */
72
  private static final Pattern TRACE_ELEMENT_PATTERN =
1✔
73
      Pattern.compile(
1✔
74
          "((?<className>.*)\\.(?<methodName>.*))\\(((?<fileName>.*?)(:(?<lineNumber>\\d+))?)\\)");
75

76
  @Override
77
  @Nonnull
78
  public TemporalFailure failureToException(
79
      @Nonnull Failure failure, @Nonnull DataConverter dataConverter) {
80
    Preconditions.checkNotNull(failure, "failure");
1✔
81
    Preconditions.checkNotNull(dataConverter, "dataConverter");
1✔
82
    TemporalFailure result = failureToExceptionImpl(failure, dataConverter);
1✔
83
    result.setFailure(failure);
1✔
84
    if (failure.getSource().equals(JAVA_SDK) && !failure.getStackTrace().isEmpty()) {
1✔
85
      StackTraceElement[] stackTrace = parseStackTrace(failure.getStackTrace());
1✔
86
      result.setStackTrace(stackTrace);
1✔
87
    }
88
    return result;
1✔
89
  }
90

91
  private TemporalFailure failureToExceptionImpl(Failure failure, DataConverter dataConverter) {
92
    TemporalFailure cause =
93
        failure.hasCause() ? failureToException(failure.getCause(), dataConverter) : null;
1✔
94
    switch (failure.getFailureInfoCase()) {
1✔
95
      case APPLICATION_FAILURE_INFO:
96
        {
97
          ApplicationFailureInfo info = failure.getApplicationFailureInfo();
1✔
98
          Optional<Payloads> details =
99
              info.hasDetails() ? Optional.of(info.getDetails()) : Optional.empty();
1✔
100
          return ApplicationFailure.newFromValues(
1✔
101
              failure.getMessage(),
1✔
102
              info.getType(),
1✔
103
              info.getNonRetryable(),
1✔
104
              new EncodedValues(details, dataConverter),
105
              cause);
106
        }
107
      case TIMEOUT_FAILURE_INFO:
108
        {
109
          TimeoutFailureInfo info = failure.getTimeoutFailureInfo();
1✔
110
          Optional<Payloads> lastHeartbeatDetails =
111
              info.hasLastHeartbeatDetails()
1✔
112
                  ? Optional.of(info.getLastHeartbeatDetails())
1✔
113
                  : Optional.empty();
1✔
114
          TimeoutFailure tf =
1✔
115
              new TimeoutFailure(
116
                  failure.getMessage(),
1✔
117
                  new EncodedValues(lastHeartbeatDetails, dataConverter),
118
                  info.getTimeoutType(),
1✔
119
                  cause);
120
          tf.setStackTrace(new StackTraceElement[0]);
1✔
121
          return tf;
1✔
122
        }
123
      case CANCELED_FAILURE_INFO:
124
        {
125
          CanceledFailureInfo info = failure.getCanceledFailureInfo();
1✔
126
          Optional<Payloads> details =
127
              info.hasDetails() ? Optional.of(info.getDetails()) : Optional.empty();
1✔
128
          return new CanceledFailure(
1✔
129
              failure.getMessage(), new EncodedValues(details, dataConverter), cause);
1✔
130
        }
131
      case TERMINATED_FAILURE_INFO:
132
        return new TerminatedFailure(failure.getMessage(), cause);
×
133
      case SERVER_FAILURE_INFO:
134
        {
135
          ServerFailureInfo info = failure.getServerFailureInfo();
×
136
          return new ServerFailure(failure.getMessage(), info.getNonRetryable(), cause);
×
137
        }
138
      case RESET_WORKFLOW_FAILURE_INFO:
139
        {
140
          ResetWorkflowFailureInfo info = failure.getResetWorkflowFailureInfo();
×
141
          Optional<Payloads> details =
142
              info.hasLastHeartbeatDetails()
×
143
                  ? Optional.of(info.getLastHeartbeatDetails())
×
144
                  : Optional.empty();
×
145
          return new ApplicationFailure(
×
146
              failure.getMessage(),
×
147
              "ResetWorkflow",
148
              false,
149
              new EncodedValues(details, dataConverter),
150
              cause);
151
        }
152
      case ACTIVITY_FAILURE_INFO:
153
        {
154
          ActivityFailureInfo info = failure.getActivityFailureInfo();
1✔
155
          return new ActivityFailure(
1✔
156
              failure.getMessage(),
1✔
157
              info.getScheduledEventId(),
1✔
158
              info.getStartedEventId(),
1✔
159
              info.getActivityType().getName(),
1✔
160
              info.getActivityId(),
1✔
161
              info.getRetryState(),
1✔
162
              info.getIdentity(),
1✔
163
              cause);
164
        }
165
      case CHILD_WORKFLOW_EXECUTION_FAILURE_INFO:
166
        {
167
          ChildWorkflowExecutionFailureInfo info = failure.getChildWorkflowExecutionFailureInfo();
1✔
168
          return new ChildWorkflowFailure(
1✔
169
              info.getInitiatedEventId(),
1✔
170
              info.getStartedEventId(),
1✔
171
              info.getWorkflowType().getName(),
1✔
172
              info.getWorkflowExecution(),
1✔
173
              info.getNamespace(),
1✔
174
              info.getRetryState(),
1✔
175
              cause);
176
        }
177
      case FAILUREINFO_NOT_SET:
178
      default:
179
        throw new IllegalArgumentException("Failure info not set");
×
180
    }
181
  }
182

183
  @Override
184
  @Nonnull
185
  public Failure exceptionToFailure(
186
      @Nonnull Throwable throwable, @Nonnull DataConverter dataConverter) {
187
    Preconditions.checkNotNull(dataConverter, "dataConverter");
1✔
188
    Preconditions.checkNotNull(throwable, "throwable");
1✔
189
    Throwable ex = throwable;
1✔
190
    while (ex != null) {
1✔
191
      if (ex instanceof TemporalFailure) {
1✔
192
        ((TemporalFailure) ex).setDataConverter(dataConverter);
1✔
193
      }
194
      ex = ex.getCause();
1✔
195
    }
196
    return this.exceptionToFailure(throwable);
1✔
197
  }
198

199
  @Nonnull
200
  private Failure exceptionToFailure(Throwable throwable) {
201
    if (throwable instanceof CheckedExceptionWrapper) {
1✔
202
      return exceptionToFailure(throwable.getCause());
×
203
    }
204
    String message;
205
    if (throwable instanceof TemporalFailure) {
1✔
206
      TemporalFailure tf = (TemporalFailure) throwable;
1✔
207
      if (tf.getFailure().isPresent()) {
1✔
208
        return tf.getFailure().get();
1✔
209
      }
210
      message = tf.getOriginalMessage();
1✔
211
    } else {
1✔
212
      message = throwable.getMessage() == null ? "" : throwable.getMessage();
1✔
213
    }
214
    String stackTrace = serializeStackTrace(throwable);
1✔
215
    Failure.Builder failure = Failure.newBuilder().setSource(JAVA_SDK);
1✔
216
    failure.setMessage(message).setStackTrace(stackTrace);
1✔
217
    if (throwable.getCause() != null) {
1✔
218
      failure.setCause(exceptionToFailure(throwable.getCause()));
1✔
219
    }
220
    if (throwable instanceof ApplicationFailure) {
1✔
221
      ApplicationFailure ae = (ApplicationFailure) throwable;
1✔
222
      ApplicationFailureInfo.Builder info =
223
          ApplicationFailureInfo.newBuilder()
1✔
224
              .setType(ae.getType())
1✔
225
              .setNonRetryable(ae.isNonRetryable());
1✔
226
      Optional<Payloads> details = ((EncodedValues) ae.getDetails()).toPayloads();
1✔
227
      if (details.isPresent()) {
1✔
228
        info.setDetails(details.get());
1✔
229
      }
230
      failure.setApplicationFailureInfo(info);
1✔
231
    } else if (throwable instanceof TimeoutFailure) {
1✔
232
      TimeoutFailure te = (TimeoutFailure) throwable;
1✔
233
      TimeoutFailureInfo.Builder info =
234
          TimeoutFailureInfo.newBuilder().setTimeoutType(te.getTimeoutType());
1✔
235
      Optional<Payloads> details = ((EncodedValues) te.getLastHeartbeatDetails()).toPayloads();
1✔
236
      if (details.isPresent()) {
1✔
237
        info.setLastHeartbeatDetails(details.get());
1✔
238
      }
239
      failure.setTimeoutFailureInfo(info);
1✔
240
    } else if (throwable instanceof CanceledFailure) {
1✔
241
      CanceledFailure ce = (CanceledFailure) throwable;
1✔
242
      CanceledFailureInfo.Builder info = CanceledFailureInfo.newBuilder();
1✔
243
      Optional<Payloads> details = ((EncodedValues) ce.getDetails()).toPayloads();
1✔
244
      if (details.isPresent()) {
1✔
245
        info.setDetails(details.get());
1✔
246
      }
247
      failure.setCanceledFailureInfo(info);
1✔
248
    } else if (throwable instanceof TerminatedFailure) {
1✔
249
      TerminatedFailure te = (TerminatedFailure) throwable;
×
250
      failure.setTerminatedFailureInfo(TerminatedFailureInfo.getDefaultInstance());
×
251
    } else if (throwable instanceof ServerFailure) {
1✔
252
      ServerFailure se = (ServerFailure) throwable;
1✔
253
      failure.setServerFailureInfo(
1✔
254
          ServerFailureInfo.newBuilder().setNonRetryable(se.isNonRetryable()));
1✔
255
    } else if (throwable instanceof ActivityFailure) {
1✔
256
      ActivityFailure ae = (ActivityFailure) throwable;
×
257
      ActivityFailureInfo.Builder info =
258
          ActivityFailureInfo.newBuilder()
×
259
              .setActivityId(ae.getActivityId() == null ? "" : ae.getActivityId())
×
260
              .setActivityType(ActivityType.newBuilder().setName(ae.getActivityType()))
×
261
              .setIdentity(ae.getIdentity())
×
262
              .setRetryState(ae.getRetryState())
×
263
              .setScheduledEventId(ae.getScheduledEventId())
×
264
              .setStartedEventId(ae.getStartedEventId());
×
265
      failure.setActivityFailureInfo(info);
×
266
    } else if (throwable instanceof ChildWorkflowFailure) {
1✔
267
      ChildWorkflowFailure ce = (ChildWorkflowFailure) throwable;
1✔
268
      ChildWorkflowExecutionFailureInfo.Builder info =
269
          ChildWorkflowExecutionFailureInfo.newBuilder()
1✔
270
              .setInitiatedEventId(ce.getInitiatedEventId())
1✔
271
              .setStartedEventId(ce.getStartedEventId())
1✔
272
              .setNamespace(ce.getNamespace() == null ? "" : ce.getNamespace())
1✔
273
              .setRetryState(ce.getRetryState())
1✔
274
              .setWorkflowType(WorkflowType.newBuilder().setName(ce.getWorkflowType()))
1✔
275
              .setWorkflowExecution(ce.getExecution());
1✔
276
      failure.setChildWorkflowExecutionFailureInfo(info);
1✔
277
    } else if (throwable instanceof ActivityCanceledException) {
1✔
278
      CanceledFailureInfo.Builder info = CanceledFailureInfo.newBuilder();
×
279
      failure.setCanceledFailureInfo(info);
×
280
    } else {
×
281
      ApplicationFailureInfo.Builder info =
282
          ApplicationFailureInfo.newBuilder()
1✔
283
              .setType(throwable.getClass().getName())
1✔
284
              .setNonRetryable(false);
1✔
285
      failure.setApplicationFailureInfo(info);
1✔
286
    }
287
    return failure.build();
1✔
288
  }
289

290
  /** Parses stack trace serialized using {@link #serializeStackTrace(Throwable)}. */
291
  private StackTraceElement[] parseStackTrace(String stackTrace) {
292
    if (Strings.isNullOrEmpty(stackTrace)) {
1✔
293
      return new StackTraceElement[0];
×
294
    }
295
    try {
296
      @SuppressWarnings("StringSplitter")
297
      String[] lines = stackTrace.split("\r\n|\n");
1✔
298
      StackTraceElement[] result = new StackTraceElement[lines.length];
1✔
299
      for (int i = 0; i < lines.length; i++) {
1✔
300
        result[i] = parseStackTraceElement(lines[i]);
1✔
301
      }
302
      return result;
1✔
303
    } catch (Exception e) {
×
304
      if (log.isWarnEnabled()) {
×
305
        log.warn("Failed to parse stack trace: " + stackTrace);
×
306
      }
307
      return new StackTraceElement[0];
×
308
    }
309
  }
310

311
  /**
312
   * See {@link StackTraceElement#toString()} for input specification.
313
   *
314
   * @param line line of stack trace.
315
   * @return StackTraceElement that contains data from that line.
316
   */
317
  private StackTraceElement parseStackTraceElement(String line) {
318
    Matcher matcher = TRACE_ELEMENT_PATTERN.matcher(line);
1✔
319
    if (!matcher.matches()) {
1✔
320
      return null;
×
321
    }
322
    String declaringClass = matcher.group("className");
1✔
323
    String methodName = matcher.group("methodName");
1✔
324
    String fileName = matcher.group("fileName");
1✔
325
    int lineNumber = 0;
1✔
326
    String lns = matcher.group("lineNumber");
1✔
327
    if (lns != null && lns.length() > 0) {
1✔
328
      try {
329
        lineNumber = Integer.parseInt(matcher.group("lineNumber"));
1✔
330
      } catch (NumberFormatException e) {
×
331
      }
1✔
332
    }
333
    return new StackTraceElement(declaringClass, methodName, fileName, lineNumber);
1✔
334
  }
335

336
  private String serializeStackTrace(Throwable e) {
337
    StringWriter sw = new StringWriter();
1✔
338
    PrintWriter pw = new PrintWriter(sw);
1✔
339
    StackTraceElement[] trace = e.getStackTrace();
1✔
340
    for (StackTraceElement element : trace) {
1✔
341
      pw.println(element);
1✔
342
      String fullMethodName = element.getClassName() + "." + element.getMethodName();
1✔
343
      if (CUTOFF_METHOD_NAMES.contains(fullMethodName)) {
1✔
344
        break;
×
345
      }
346
    }
347
    return sw.toString();
1✔
348
  }
349
}
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