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

temporalio / sdk-java / #333

16 Oct 2024 07:28PM UTC coverage: 78.65% (+0.6%) from 78.085%
#333

push

github

web-flow
Fix code coverage (#2275)

22670 of 28824 relevant lines covered (78.65%)

0.79 hits per line

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

79.7
/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.*;
30
import io.temporal.client.ActivityCanceledException;
31
import io.temporal.common.converter.DataConverter;
32
import io.temporal.common.converter.EncodedValues;
33
import io.temporal.common.converter.FailureConverter;
34
import io.temporal.internal.activity.ActivityTaskHandlerImpl;
35
import io.temporal.internal.common.ProtobufTimeUtils;
36
import io.temporal.internal.sync.POJOWorkflowImplementationFactory;
37
import io.temporal.serviceclient.CheckedExceptionWrapper;
38
import java.io.PrintWriter;
39
import java.io.StringWriter;
40
import java.util.ArrayList;
41
import java.util.Optional;
42
import java.util.regex.Matcher;
43
import java.util.regex.Pattern;
44
import javax.annotation.Nonnull;
45
import org.slf4j.Logger;
46
import org.slf4j.LoggerFactory;
47

48
/**
49
 * A {@link FailureConverter} that implements the default cross-language-compatible conversion
50
 * algorithm.
51
 */
52
public final class DefaultFailureConverter implements FailureConverter {
1✔
53

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

56
  private static final String JAVA_SDK = "JavaSDK";
57

58
  /**
59
   * Stop emitting stack trace after this line. Makes serialized stack traces more readable and
60
   * compact as it omits most of framework-level code.
61
   */
62
  private static final ImmutableSet<String> CUTOFF_METHOD_NAMES =
63
      ImmutableSet.<String>builder()
1✔
64
          .addAll(ActivityTaskHandlerImpl.ACTIVITY_HANDLER_STACKTRACE_CUTOFF)
1✔
65
          .addAll(POJOWorkflowImplementationFactory.WORKFLOW_HANDLER_STACKTRACE_CUTOFF)
1✔
66
          .build();
1✔
67

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

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

88
  private TemporalFailure failureToExceptionImpl(Failure failure, DataConverter dataConverter) {
89
    TemporalFailure cause =
90
        failure.hasCause() ? failureToException(failure.getCause(), dataConverter) : null;
1✔
91
    switch (failure.getFailureInfoCase()) {
1✔
92
      case APPLICATION_FAILURE_INFO:
93
        {
94
          ApplicationFailureInfo info = failure.getApplicationFailureInfo();
1✔
95
          Optional<Payloads> details =
96
              info.hasDetails() ? Optional.of(info.getDetails()) : Optional.empty();
1✔
97
          return ApplicationFailure.newFromValues(
1✔
98
              failure.getMessage(),
1✔
99
              info.getType(),
1✔
100
              info.getNonRetryable(),
1✔
101
              new EncodedValues(details, dataConverter),
102
              cause,
103
              info.hasNextRetryDelay()
1✔
104
                  ? ProtobufTimeUtils.toJavaDuration(info.getNextRetryDelay())
×
105
                  : null);
1✔
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
              null);
152
        }
153
      case ACTIVITY_FAILURE_INFO:
154
        {
155
          ActivityFailureInfo info = failure.getActivityFailureInfo();
1✔
156
          return new ActivityFailure(
1✔
157
              failure.getMessage(),
1✔
158
              info.getScheduledEventId(),
1✔
159
              info.getStartedEventId(),
1✔
160
              info.getActivityType().getName(),
1✔
161
              info.getActivityId(),
1✔
162
              info.getRetryState(),
1✔
163
              info.getIdentity(),
1✔
164
              cause);
165
        }
166
      case CHILD_WORKFLOW_EXECUTION_FAILURE_INFO:
167
        {
168
          ChildWorkflowExecutionFailureInfo info = failure.getChildWorkflowExecutionFailureInfo();
1✔
169
          return new ChildWorkflowFailure(
1✔
170
              info.getInitiatedEventId(),
1✔
171
              info.getStartedEventId(),
1✔
172
              info.getWorkflowType().getName(),
1✔
173
              info.getWorkflowExecution(),
1✔
174
              info.getNamespace(),
1✔
175
              info.getRetryState(),
1✔
176
              cause);
177
        }
178
      case NEXUS_OPERATION_EXECUTION_FAILURE_INFO:
179
        {
180
          NexusOperationFailureInfo info = failure.getNexusOperationExecutionFailureInfo();
1✔
181
          return new NexusOperationFailure(
1✔
182
              failure.getMessage(),
1✔
183
              info.getScheduledEventId(),
1✔
184
              info.getEndpoint(),
1✔
185
              info.getService(),
1✔
186
              info.getOperation(),
1✔
187
              info.getOperationId(),
1✔
188
              cause);
189
        }
190
      case FAILUREINFO_NOT_SET:
191
      default:
192
        throw new IllegalArgumentException("Failure info not set");
×
193
    }
194
  }
195

196
  @Override
197
  @Nonnull
198
  public Failure exceptionToFailure(
199
      @Nonnull Throwable throwable, @Nonnull DataConverter dataConverter) {
200
    Preconditions.checkNotNull(dataConverter, "dataConverter");
1✔
201
    Preconditions.checkNotNull(throwable, "throwable");
1✔
202
    Throwable ex = throwable;
1✔
203
    while (ex != null) {
1✔
204
      if (ex instanceof TemporalFailure) {
1✔
205
        ((TemporalFailure) ex).setDataConverter(dataConverter);
1✔
206
      }
207
      ex = ex.getCause();
1✔
208
    }
209
    return this.exceptionToFailure(throwable);
1✔
210
  }
211

212
  @Nonnull
213
  private Failure exceptionToFailure(Throwable throwable) {
214
    if (throwable instanceof CheckedExceptionWrapper) {
1✔
215
      return exceptionToFailure(throwable.getCause());
×
216
    }
217
    String message;
218
    if (throwable instanceof TemporalFailure) {
1✔
219
      TemporalFailure tf = (TemporalFailure) throwable;
1✔
220
      if (tf.getFailure().isPresent()) {
1✔
221
        return tf.getFailure().get();
1✔
222
      }
223
      message = tf.getOriginalMessage();
1✔
224
    } else {
1✔
225
      message = throwable.getMessage() == null ? "" : throwable.getMessage();
1✔
226
    }
227
    String stackTrace = serializeStackTrace(throwable);
1✔
228
    Failure.Builder failure = Failure.newBuilder().setSource(JAVA_SDK);
1✔
229
    failure.setMessage(message).setStackTrace(stackTrace);
1✔
230
    if (throwable.getCause() != null) {
1✔
231
      failure.setCause(exceptionToFailure(throwable.getCause()));
1✔
232
    }
233
    if (throwable instanceof ApplicationFailure) {
1✔
234
      ApplicationFailure ae = (ApplicationFailure) throwable;
1✔
235
      ApplicationFailureInfo.Builder info =
236
          ApplicationFailureInfo.newBuilder()
1✔
237
              .setType(ae.getType())
1✔
238
              .setNonRetryable(ae.isNonRetryable());
1✔
239
      Optional<Payloads> details = ((EncodedValues) ae.getDetails()).toPayloads();
1✔
240
      if (details.isPresent()) {
1✔
241
        info.setDetails(details.get());
1✔
242
      }
243
      if (ae.getNextRetryDelay() != null) {
1✔
244
        info.setNextRetryDelay(ProtobufTimeUtils.toProtoDuration(ae.getNextRetryDelay()));
1✔
245
      }
246
      failure.setApplicationFailureInfo(info);
1✔
247
    } else if (throwable instanceof TimeoutFailure) {
1✔
248
      TimeoutFailure te = (TimeoutFailure) throwable;
1✔
249
      TimeoutFailureInfo.Builder info =
250
          TimeoutFailureInfo.newBuilder().setTimeoutType(te.getTimeoutType());
1✔
251
      Optional<Payloads> details = ((EncodedValues) te.getLastHeartbeatDetails()).toPayloads();
1✔
252
      if (details.isPresent()) {
1✔
253
        info.setLastHeartbeatDetails(details.get());
1✔
254
      }
255
      failure.setTimeoutFailureInfo(info);
1✔
256
    } else if (throwable instanceof CanceledFailure) {
1✔
257
      CanceledFailure ce = (CanceledFailure) throwable;
1✔
258
      CanceledFailureInfo.Builder info = CanceledFailureInfo.newBuilder();
1✔
259
      Optional<Payloads> details = ((EncodedValues) ce.getDetails()).toPayloads();
1✔
260
      if (details.isPresent()) {
1✔
261
        info.setDetails(details.get());
1✔
262
      }
263
      failure.setCanceledFailureInfo(info);
1✔
264
    } else if (throwable instanceof TerminatedFailure) {
1✔
265
      TerminatedFailure te = (TerminatedFailure) throwable;
×
266
      failure.setTerminatedFailureInfo(TerminatedFailureInfo.getDefaultInstance());
×
267
    } else if (throwable instanceof ServerFailure) {
1✔
268
      ServerFailure se = (ServerFailure) throwable;
1✔
269
      failure.setServerFailureInfo(
1✔
270
          ServerFailureInfo.newBuilder().setNonRetryable(se.isNonRetryable()));
1✔
271
    } else if (throwable instanceof ActivityFailure) {
1✔
272
      ActivityFailure ae = (ActivityFailure) throwable;
×
273
      ActivityFailureInfo.Builder info =
274
          ActivityFailureInfo.newBuilder()
×
275
              .setActivityId(ae.getActivityId() == null ? "" : ae.getActivityId())
×
276
              .setActivityType(ActivityType.newBuilder().setName(ae.getActivityType()))
×
277
              .setIdentity(ae.getIdentity())
×
278
              .setRetryState(ae.getRetryState())
×
279
              .setScheduledEventId(ae.getScheduledEventId())
×
280
              .setStartedEventId(ae.getStartedEventId());
×
281
      failure.setActivityFailureInfo(info);
×
282
    } else if (throwable instanceof ChildWorkflowFailure) {
1✔
283
      ChildWorkflowFailure ce = (ChildWorkflowFailure) throwable;
1✔
284
      ChildWorkflowExecutionFailureInfo.Builder info =
285
          ChildWorkflowExecutionFailureInfo.newBuilder()
1✔
286
              .setInitiatedEventId(ce.getInitiatedEventId())
1✔
287
              .setStartedEventId(ce.getStartedEventId())
1✔
288
              .setNamespace(ce.getNamespace() == null ? "" : ce.getNamespace())
1✔
289
              .setRetryState(ce.getRetryState())
1✔
290
              .setWorkflowType(WorkflowType.newBuilder().setName(ce.getWorkflowType()))
1✔
291
              .setWorkflowExecution(ce.getExecution());
1✔
292
      failure.setChildWorkflowExecutionFailureInfo(info);
1✔
293
    } else if (throwable instanceof ActivityCanceledException) {
1✔
294
      CanceledFailureInfo.Builder info = CanceledFailureInfo.newBuilder();
×
295
      failure.setCanceledFailureInfo(info);
×
296
    } else if (throwable instanceof NexusOperationFailure) {
1✔
297
      NexusOperationFailure no = (NexusOperationFailure) throwable;
×
298
      NexusOperationFailureInfo.Builder info =
299
          NexusOperationFailureInfo.newBuilder()
×
300
              .setScheduledEventId(no.getScheduledEventId())
×
301
              .setEndpoint(no.getEndpoint())
×
302
              .setService(no.getService())
×
303
              .setOperation(no.getOperation())
×
304
              .setOperationId(no.getOperationId());
×
305
      failure.setNexusOperationExecutionFailureInfo(info);
×
306
    } else {
×
307
      ApplicationFailureInfo.Builder info =
308
          ApplicationFailureInfo.newBuilder()
1✔
309
              .setType(throwable.getClass().getName())
1✔
310
              .setNonRetryable(false);
1✔
311
      failure.setApplicationFailureInfo(info);
1✔
312
    }
313
    return failure.build();
1✔
314
  }
315

316
  /** Parses stack trace serialized using {@link #serializeStackTrace(Throwable)}. */
317
  private StackTraceElement[] parseStackTrace(String stackTrace) {
318
    if (Strings.isNullOrEmpty(stackTrace)) {
1✔
319
      return new StackTraceElement[0];
×
320
    }
321
    try {
322
      @SuppressWarnings("StringSplitter")
323
      String[] lines = stackTrace.split("\r\n|\n");
1✔
324
      ArrayList<StackTraceElement> result = new ArrayList<>(lines.length);
1✔
325
      for (int i = 0; i < lines.length; i++) {
1✔
326
        StackTraceElement elem = parseStackTraceElement(lines[i]);
1✔
327
        if (elem != null) {
1✔
328
          result.add(elem);
1✔
329
        }
330
      }
331
      return result.toArray(new StackTraceElement[result.size()]);
1✔
332
    } catch (Exception e) {
×
333
      if (log.isWarnEnabled()) {
×
334
        log.warn("Failed to parse stack trace: " + stackTrace);
×
335
      }
336
      return new StackTraceElement[0];
×
337
    }
338
  }
339

340
  /**
341
   * See {@link StackTraceElement#toString()} for input specification.
342
   *
343
   * @param line line of stack trace.
344
   * @return StackTraceElement that contains data from that line.
345
   */
346
  private StackTraceElement parseStackTraceElement(String line) {
347
    Matcher matcher = TRACE_ELEMENT_PATTERN.matcher(line);
1✔
348
    if (!matcher.matches()) {
1✔
349
      return null;
×
350
    }
351
    String declaringClass = matcher.group("className");
1✔
352
    String methodName = matcher.group("methodName");
1✔
353
    String fileName = matcher.group("fileName");
1✔
354
    int lineNumber = 0;
1✔
355
    String lns = matcher.group("lineNumber");
1✔
356
    if (lns != null && lns.length() > 0) {
1✔
357
      try {
358
        lineNumber = Integer.parseInt(matcher.group("lineNumber"));
1✔
359
      } catch (NumberFormatException e) {
×
360
      }
1✔
361
    }
362
    return new StackTraceElement(declaringClass, methodName, fileName, lineNumber);
1✔
363
  }
364

365
  private String serializeStackTrace(Throwable e) {
366
    StringWriter sw = new StringWriter();
1✔
367
    PrintWriter pw = new PrintWriter(sw);
1✔
368
    StackTraceElement[] trace = e.getStackTrace();
1✔
369
    for (StackTraceElement element : trace) {
1✔
370
      pw.println(element);
1✔
371
      String fullMethodName = element.getClassName() + "." + element.getMethodName();
1✔
372
      if (CUTOFF_METHOD_NAMES.contains(fullMethodName)) {
1✔
373
        break;
1✔
374
      }
375
    }
376
    return sw.toString();
1✔
377
  }
378
}
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