diff --git a/build.gradle b/build.gradle
index 127d55e318..601e95229e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -102,6 +102,9 @@ dependencies {
implementation group: 'com.cronutils', name: 'cron-utils', version: '9.0.0'
implementation 'io.grpc:grpc-netty-shaded:1.29.0'
implementation group: 'com.google.protobuf', name: 'protobuf-java-util', version: '3.12.1'
+ implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.3'
+ implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.11.0'
+
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
testImplementation group: 'com.googlecode.junit-toolbox', name: 'junit-toolbox', version: '2.4'
diff --git a/src/main/java/io/temporal/activity/Activity.java b/src/main/java/io/temporal/activity/Activity.java
index 0cc1000cde..1aa5973cb1 100644
--- a/src/main/java/io/temporal/activity/Activity.java
+++ b/src/main/java/io/temporal/activity/Activity.java
@@ -20,12 +20,13 @@
package io.temporal.activity;
import io.temporal.client.ActivityCompletionException;
+import io.temporal.failure.ActivityFailure;
+import io.temporal.failure.ChildWorkflowFailure;
+import io.temporal.failure.TimeoutFailure;
import io.temporal.internal.sync.ActivityInternal;
import io.temporal.internal.sync.WorkflowInternal;
import io.temporal.proto.common.WorkflowExecution;
import io.temporal.serviceclient.WorkflowServiceStubs;
-import io.temporal.workflow.ActivityException;
-import io.temporal.workflow.ActivityTimeoutException;
import java.lang.reflect.Type;
import java.util.Optional;
@@ -234,7 +235,7 @@ public static ActivityTask getTask() {
* Use to notify Temporal service that activity execution is alive.
*
* @param details In case of activity timeout can be accessed through {@link
- * ActivityTimeoutException#getDetails(Class)} method.
+ * TimeoutFailure#getLastHeartbeatDetails()} method.
* @throws ActivityCompletionException Indicates that activity execution is expected to be
* interrupted. The reason for interruption is indicated by a type of subclass of the
* exception.
@@ -291,10 +292,10 @@ public static String getNamespace() {
*
The reason for such design is that returning originally thrown exception from a remote call
* (which child workflow and activity invocations are ) would not allow adding context information
* about a failure, like activity and child workflow id. So stubs always throw a subclass of
- * {@link ActivityException} from calls to an activity and subclass of {@link
- * io.temporal.workflow.ChildWorkflowException} from calls to a child workflow. The original
- * exception is attached as a cause to these wrapper exceptions. So as exceptions are always
- * wrapped adding checked ones to method signature causes more pain than benefit.
+ * {@link ActivityFailure} from calls to an activity and subclass of {@link ChildWorkflowFailure}
+ * from calls to a child workflow. The original exception is attached as a cause to these wrapper
+ * exceptions. So as exceptions are always wrapped adding checked ones to method signature causes
+ * more pain than benefit.
*
*
Throws original exception if e is {@link RuntimeException} or {@link Error}. Never returns.
* But return type is not empty to be able to use it as:
diff --git a/src/main/java/io/temporal/activity/ActivityCancellationType.java b/src/main/java/io/temporal/activity/ActivityCancellationType.java
index 784d95eff7..0fbb828c9b 100644
--- a/src/main/java/io/temporal/activity/ActivityCancellationType.java
+++ b/src/main/java/io/temporal/activity/ActivityCancellationType.java
@@ -19,13 +19,13 @@
package io.temporal.activity;
+import io.temporal.failure.CanceledFailure;
import io.temporal.workflow.CancellationScope;
-import java.util.concurrent.CancellationException;
/**
* Defines behaviour of the parent workflow when {@link CancellationScope} that wraps child workflow
* execution request is cancelled. The result of the cancellation independently of the type is a
- * {@link CancellationException} thrown from the child workflow method.
+ * {@link CanceledFailure} thrown from the child workflow method.
*/
public enum ActivityCancellationType {
/**
diff --git a/src/main/java/io/temporal/activity/ActivityOptions.java b/src/main/java/io/temporal/activity/ActivityOptions.java
index fc8bb861c9..90c40acdd3 100644
--- a/src/main/java/io/temporal/activity/ActivityOptions.java
+++ b/src/main/java/io/temporal/activity/ActivityOptions.java
@@ -23,9 +23,9 @@
import io.temporal.common.MethodRetry;
import io.temporal.common.RetryOptions;
import io.temporal.common.context.ContextPropagator;
+import io.temporal.failure.CanceledFailure;
import java.time.Duration;
import java.util.List;
-import java.util.concurrent.CancellationException;
/** Options used to configure how an activity is invoked. */
public final class ActivityOptions {
@@ -145,9 +145,9 @@ public Builder setContextPropagators(List contextPropagators)
}
/**
- * In case of an activity cancellation it fails with a {@link CancellationException}Exception.
- * If this flag is set to false then the exception is thrown not immediately but only after an
- * activity completes its cleanup. If true a CancellationException is thrown immediately and an
+ * In case of an activity cancellation it fails with a {@link CanceledFailure}. If this flag is
+ * set to false then the exception is thrown not immediately but only after an activity
+ * completes its cleanup. If true a {@link CanceledFailure} is thrown immediately and an
* activity cancellation is going to happen in the background.
*/
public Builder setCancellationType(ActivityCancellationType cancellationType) {
diff --git a/src/main/java/io/temporal/client/ActivityCompletionClient.java b/src/main/java/io/temporal/client/ActivityCompletionClient.java
index d363412389..488a363a19 100644
--- a/src/main/java/io/temporal/client/ActivityCompletionClient.java
+++ b/src/main/java/io/temporal/client/ActivityCompletionClient.java
@@ -20,8 +20,8 @@
package io.temporal.client;
import io.temporal.activity.Activity;
+import io.temporal.failure.CanceledFailure;
import io.temporal.proto.common.WorkflowExecution;
-import java.util.concurrent.CancellationException;
/**
* Used to complete asynchronously activities that called {@link Activity#doNotCompleteOnReturn()}.
@@ -52,7 +52,7 @@ void reportCancellation(WorkflowExecution execution, String activityId, V de
/**
* Warning: heartbeating by ids is not implemented yet.
*
- * @throws CancellationException if activity is cancelled.
+ * @throws CanceledFailure if activity is cancelled.
*/
void heartbeat(WorkflowExecution execution, String activityId, V details)
throws ActivityCompletionException;
diff --git a/src/main/java/io/temporal/client/ActivityCompletionException.java b/src/main/java/io/temporal/client/ActivityCompletionException.java
index 6acfb2bc40..566498f1ba 100644
--- a/src/main/java/io/temporal/client/ActivityCompletionException.java
+++ b/src/main/java/io/temporal/client/ActivityCompletionException.java
@@ -20,10 +20,11 @@
package io.temporal.client;
import io.temporal.activity.ActivityTask;
+import io.temporal.failure.TemporalException;
import io.temporal.proto.common.WorkflowExecution;
-/** Base exception for all failures returned by an activity completion client. */
-public class ActivityCompletionException extends RuntimeException {
+/** Base exception for all failures returned by an activity completion client. Do not extend! */
+public class ActivityCompletionException extends TemporalException {
private final WorkflowExecution execution;
@@ -32,9 +33,7 @@ public class ActivityCompletionException extends RuntimeException {
private final String activityId;
protected ActivityCompletionException(ActivityTask task) {
- execution = task.getWorkflowExecution();
- activityType = task.getActivityType();
- activityId = task.getActivityId();
+ this(task, null);
}
protected ActivityCompletionException(ActivityTask task, Throwable cause) {
@@ -60,7 +59,7 @@ protected ActivityCompletionException(ActivityTask task, Throwable cause) {
}
protected ActivityCompletionException(String activityId, Throwable cause) {
- super("ActivityId" + activityId, cause);
+ super("ActivityId=" + activityId, cause);
this.execution = null;
this.activityType = null;
this.activityId = activityId;
@@ -71,7 +70,7 @@ protected ActivityCompletionException(Throwable cause) {
}
protected ActivityCompletionException() {
- super();
+ super(null, null);
execution = null;
activityType = null;
activityId = null;
diff --git a/src/main/java/io/temporal/client/WorkflowClient.java b/src/main/java/io/temporal/client/WorkflowClient.java
index 8eab736086..18aa875ffb 100644
--- a/src/main/java/io/temporal/client/WorkflowClient.java
+++ b/src/main/java/io/temporal/client/WorkflowClient.java
@@ -87,10 +87,9 @@
* If you need to wait for a workflow completion after an asynchronous start, maybe even from a
* different process, the simplest way is to call the blocking version again. If {@link
* WorkflowOptions#getWorkflowIdReusePolicy()} is not {@code AllowDuplicate} then instead of
- * throwing {@link io.temporal.client.DuplicateWorkflowException}, it reconnects to an existing
- * workflow and waits for its completion. The following example shows how to do this from a
- * different process than the one that started the workflow. All this process needs is a {@code
- * WorkflowId}.
+ * throwing {@link WorkflowExecutionAlreadyStarted}, it reconnects to an existing workflow and waits
+ * for its completion. The following example shows how to do this from a different process than the
+ * one that started the workflow. All this process needs is a {@code WorkflowId}.
*
*
* FileProcessingWorkflow workflow = workflowClient.newWorkflowStub(FileProcessingWorkflow.class, workflowId);
diff --git a/src/main/java/io/temporal/client/WorkflowClientOptions.java b/src/main/java/io/temporal/client/WorkflowClientOptions.java
index dd298c1483..984eb1ea5f 100644
--- a/src/main/java/io/temporal/client/WorkflowClientOptions.java
+++ b/src/main/java/io/temporal/client/WorkflowClientOptions.java
@@ -21,7 +21,6 @@
import io.temporal.common.context.ContextPropagator;
import io.temporal.common.converter.DataConverter;
-import io.temporal.common.converter.GsonJsonDataConverter;
import io.temporal.proto.query.QueryRejectCondition;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
@@ -82,7 +81,7 @@ public Builder setNamespace(String namespace) {
* Overrides a data converter implementation used serialize workflow and activity arguments and
* results.
*
- *
Default is {@link GsonJsonDataConverter} data converter.
+ *
Default is {@link DataConverter#getDefaultInstance()}.
*/
public Builder setDataConverter(DataConverter dataConverter) {
this.dataConverter = Objects.requireNonNull(dataConverter);
@@ -155,7 +154,7 @@ public WorkflowClientOptions validateAndBuildWithDefaults() {
}
return new WorkflowClientOptions(
namespace == null ? DEFAULT_NAMESPACE : namespace,
- dataConverter == null ? GsonJsonDataConverter.getInstance() : dataConverter,
+ dataConverter == null ? DataConverter.getDefaultInstance() : dataConverter,
interceptors == null ? EMPTY_INTERCEPTOR_ARRAY : interceptors,
name,
contextPropagators == null ? EMPTY_CONTEXT_PROPAGATORS : contextPropagators,
diff --git a/src/main/java/io/temporal/client/WorkflowException.java b/src/main/java/io/temporal/client/WorkflowException.java
index dfa1aee22c..07db5aedc8 100644
--- a/src/main/java/io/temporal/client/WorkflowException.java
+++ b/src/main/java/io/temporal/client/WorkflowException.java
@@ -19,46 +19,28 @@
package io.temporal.client;
+import io.temporal.failure.TemporalException;
import io.temporal.proto.common.WorkflowExecution;
-import io.temporal.workflow.ChildWorkflowException;
+import java.util.Objects;
import java.util.Optional;
-/**
- * Base exception for all workflow failures returned by an external client. Note that inside a
- * workflow implementation child workflows throw subclasses of {@link ChildWorkflowException}.
- */
-public class WorkflowException extends RuntimeException {
+/** Base exception for all workflow failures. */
+public abstract class WorkflowException extends TemporalException {
private final WorkflowExecution execution;
private final Optional workflowType;
- protected WorkflowException(
- String message, WorkflowExecution execution, Optional workflowType, Throwable cause) {
- super(getMessage(message, execution, workflowType), cause);
- this.execution = execution;
- this.workflowType = workflowType;
+ protected WorkflowException(WorkflowExecution execution, String workflowType, Throwable cause) {
+ super(getMessage(execution, workflowType), cause);
+ this.execution = Objects.requireNonNull(execution);
+ this.workflowType = Optional.ofNullable(workflowType);
}
- private static String getMessage(
- String message, WorkflowExecution execution, Optional workflowType) {
- StringBuilder result = new StringBuilder();
- if (message != null) {
- result.append(message);
- result.append(", ");
- }
- if (workflowType.isPresent()) {
- result.append("WorkflowType=\"");
- result.append(workflowType.get());
- }
- if (execution != null) {
- if (result.length() > 0) {
- result.append("\", ");
- }
- result.append("WorkflowExecution=\"");
- result.append(execution);
- result.append("\"");
- }
- return result.toString();
+ protected WorkflowException(
+ String message, WorkflowExecution execution, String workflowType, Throwable cause) {
+ super(message, cause);
+ this.execution = Objects.requireNonNull(execution);
+ this.workflowType = Optional.ofNullable(workflowType);
}
public WorkflowExecution getExecution() {
@@ -68,4 +50,13 @@ public WorkflowExecution getExecution() {
public Optional getWorkflowType() {
return workflowType;
}
+
+ public static String getMessage(WorkflowExecution execution, String workflowType) {
+ return "workflowId='"
+ + execution.getWorkflowId()
+ + "', runId='"
+ + execution.getRunId()
+ + (workflowType == null ? "" : "', workflowType='" + workflowType + '\'')
+ + '}';
+ }
}
diff --git a/src/main/java/io/temporal/client/DuplicateWorkflowException.java b/src/main/java/io/temporal/client/WorkflowExecutionAlreadyStarted.java
similarity index 86%
rename from src/main/java/io/temporal/client/DuplicateWorkflowException.java
rename to src/main/java/io/temporal/client/WorkflowExecutionAlreadyStarted.java
index 2459e28d70..f8bfcae241 100644
--- a/src/main/java/io/temporal/client/DuplicateWorkflowException.java
+++ b/src/main/java/io/temporal/client/WorkflowExecutionAlreadyStarted.java
@@ -20,7 +20,6 @@
package io.temporal.client;
import io.temporal.proto.common.WorkflowExecution;
-import java.util.Optional;
/**
* This exception is thrown in the following cases:
@@ -50,10 +49,9 @@
* io.temporal.proto.common.WorkflowIdReusePolicy#AllowDuplicate}
*
*/
-public final class DuplicateWorkflowException extends WorkflowException {
-
- public DuplicateWorkflowException(
- WorkflowExecution execution, String workflowType, String message) {
- super(message, execution, Optional.of(workflowType), null);
+public final class WorkflowExecutionAlreadyStarted extends WorkflowException {
+ public WorkflowExecutionAlreadyStarted(
+ WorkflowExecution execution, String workflowType, Throwable cause) {
+ super(execution, workflowType, cause);
}
}
diff --git a/src/main/java/io/temporal/client/WorkflowFailureException.java b/src/main/java/io/temporal/client/WorkflowFailedException.java
similarity index 52%
rename from src/main/java/io/temporal/client/WorkflowFailureException.java
rename to src/main/java/io/temporal/client/WorkflowFailedException.java
index 9247bb6493..fc05ef126f 100644
--- a/src/main/java/io/temporal/client/WorkflowFailureException.java
+++ b/src/main/java/io/temporal/client/WorkflowFailedException.java
@@ -19,41 +19,54 @@
package io.temporal.client;
+import io.temporal.proto.common.RetryStatus;
import io.temporal.proto.common.WorkflowExecution;
-import java.util.Optional;
/**
* Indicates that a workflow failed. An original cause of the workflow failure can be retrieved
* through {@link #getCause()}.
*/
-public final class WorkflowFailureException extends WorkflowException {
+public final class WorkflowFailedException extends WorkflowException {
+ private final RetryStatus retryStatus;
private final long decisionTaskCompletedEventId;
- public WorkflowFailureException(
- WorkflowExecution execution,
- Optional workflowType,
+ public WorkflowFailedException(
+ WorkflowExecution workflowExecution,
+ String workflowType,
long decisionTaskCompletedEventId,
- Throwable failure) {
- super(getMessage(execution, workflowType), execution, workflowType, failure);
+ RetryStatus retryStatus,
+ Throwable cause) {
+ super(
+ getMessage(workflowExecution, workflowType, decisionTaskCompletedEventId, retryStatus),
+ workflowExecution,
+ workflowType,
+ cause);
+ this.retryStatus = retryStatus;
this.decisionTaskCompletedEventId = decisionTaskCompletedEventId;
}
- private static String getMessage(WorkflowExecution execution, Optional workflowType) {
- StringBuilder result = new StringBuilder();
- if (workflowType.isPresent()) {
- result.append("WorkflowType=\"");
- result.append(workflowType.get());
- result.append("\", ");
- }
- result.append("WorkflowId=\"");
- result.append(execution.getWorkflowId());
- result.append("\", RunId=\"");
- result.append(execution.getRunId());
- return result.toString();
+ public RetryStatus getRetryStatus() {
+ return retryStatus;
}
public long getDecisionTaskCompletedEventId() {
return decisionTaskCompletedEventId;
}
+
+ public static String getMessage(
+ WorkflowExecution workflowExecution,
+ String workflowType,
+ long decisionTaskCompletedEventId,
+ RetryStatus retryStatus) {
+ return "workflowId='"
+ + workflowExecution.getWorkflowId()
+ + "', runId='"
+ + workflowExecution.getRunId()
+ + (workflowType == null ? "'" : "', workflowType='" + workflowType + '\'')
+ + ", retryStatus="
+ + retryStatus
+ + ", decisionTaskCompletedEventId="
+ + decisionTaskCompletedEventId;
+ }
}
diff --git a/src/main/java/io/temporal/client/WorkflowNotFoundException.java b/src/main/java/io/temporal/client/WorkflowNotFoundException.java
index 5beeca03cf..f00cef3f2d 100644
--- a/src/main/java/io/temporal/client/WorkflowNotFoundException.java
+++ b/src/main/java/io/temporal/client/WorkflowNotFoundException.java
@@ -20,7 +20,6 @@
package io.temporal.client;
import io.temporal.proto.common.WorkflowExecution;
-import java.util.Optional;
/**
* Thrown when workflow with the given id is not known to the Temporal service. It could be because
@@ -28,8 +27,7 @@
*/
public final class WorkflowNotFoundException extends WorkflowException {
- public WorkflowNotFoundException(
- WorkflowExecution execution, Optional workflowType, String message) {
- super(message, execution, workflowType, null);
+ public WorkflowNotFoundException(WorkflowExecution execution, String workflowType) {
+ super(execution, workflowType, null);
}
}
diff --git a/src/main/java/io/temporal/client/WorkflowQueryException.java b/src/main/java/io/temporal/client/WorkflowQueryException.java
index 1a5d357aa9..fb6d91aa11 100644
--- a/src/main/java/io/temporal/client/WorkflowQueryException.java
+++ b/src/main/java/io/temporal/client/WorkflowQueryException.java
@@ -20,11 +20,10 @@
package io.temporal.client;
import io.temporal.proto.common.WorkflowExecution;
-import java.util.Optional;
public class WorkflowQueryException extends WorkflowException {
- public WorkflowQueryException(WorkflowExecution execution, String message) {
- super(message, execution, Optional.empty(), null);
+ public WorkflowQueryException(WorkflowExecution execution, String workflowType, Throwable cause) {
+ super(execution, workflowType, cause);
}
}
diff --git a/src/main/java/io/temporal/client/WorkflowQueryRejectedException.java b/src/main/java/io/temporal/client/WorkflowQueryRejectedException.java
index 911ee34c85..81cb49f8e9 100644
--- a/src/main/java/io/temporal/client/WorkflowQueryRejectedException.java
+++ b/src/main/java/io/temporal/client/WorkflowQueryRejectedException.java
@@ -30,14 +30,11 @@ public final class WorkflowQueryRejectedException extends WorkflowQueryException
public WorkflowQueryRejectedException(
WorkflowExecution execution,
+ String workflowType,
QueryRejectCondition queryRejectCondition,
- WorkflowExecutionStatus workflowExecutionStatus) {
- super(
- execution,
- "Query invoked with "
- + queryRejectCondition
- + " reject condition. The workflow execution status is "
- + workflowExecutionStatus);
+ WorkflowExecutionStatus workflowExecutionStatus,
+ Throwable cause) {
+ super(execution, workflowType, cause);
this.queryRejectCondition = queryRejectCondition;
this.workflowExecutionStatus = workflowExecutionStatus;
}
diff --git a/src/main/java/io/temporal/client/WorkflowServiceException.java b/src/main/java/io/temporal/client/WorkflowServiceException.java
index f40009ee69..fe9582c1cc 100644
--- a/src/main/java/io/temporal/client/WorkflowServiceException.java
+++ b/src/main/java/io/temporal/client/WorkflowServiceException.java
@@ -19,14 +19,11 @@
package io.temporal.client;
-import io.temporal.internal.common.CheckedExceptionWrapper;
import io.temporal.proto.common.WorkflowExecution;
-import java.util.Optional;
-
-public final class WorkflowServiceException extends WorkflowException {
+public class WorkflowServiceException extends WorkflowException {
public WorkflowServiceException(
- WorkflowExecution execution, Optional workflowType, Throwable failure) {
- super(null, execution, workflowType, CheckedExceptionWrapper.unwrap(failure));
+ WorkflowExecution execution, String workflowType, Throwable cause) {
+ super(execution, workflowType, cause);
}
}
diff --git a/src/main/java/io/temporal/client/WorkflowTerminatedException.java b/src/main/java/io/temporal/client/WorkflowTerminatedException.java
deleted file mode 100644
index 1cdc510391..0000000000
--- a/src/main/java/io/temporal/client/WorkflowTerminatedException.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
- *
- * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Modifications copyright (C) 2017 Uber Technologies, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"). You may not
- * use this file except in compliance with the License. A copy of the License is
- * located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package io.temporal.client;
-
-import io.temporal.proto.common.WorkflowExecution;
-import java.util.Optional;
-
-/**
- * Indicates that a workflow was forcefully terminated by an external command to Temporal service.
- */
-public final class WorkflowTerminatedException extends WorkflowException {
-
- private final byte[] details;
- private final String identity;
-
- public WorkflowTerminatedException(
- WorkflowExecution execution,
- Optional workflowType,
- String reason,
- String identity,
- byte[] details) {
- super("Terminated by " + identity + " for \"" + reason + "\"", execution, workflowType, null);
- this.identity = identity;
- this.details = details;
- }
-
- public String getIdentity() {
- return identity;
- }
-
- public byte[] getDetails() {
- return details;
- }
-}
diff --git a/src/main/java/io/temporal/client/WorkflowTimedOutException.java b/src/main/java/io/temporal/client/WorkflowTimedOutException.java
deleted file mode 100644
index 4e622d36f2..0000000000
--- a/src/main/java/io/temporal/client/WorkflowTimedOutException.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
- *
- * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Modifications copyright (C) 2017 Uber Technologies, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"). You may not
- * use this file except in compliance with the License. A copy of the License is
- * located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package io.temporal.client;
-
-import io.temporal.proto.common.TimeoutType;
-import io.temporal.proto.common.WorkflowExecution;
-import java.util.Optional;
-
-/**
- * Indicates that a workflow exceeded its execution timeout and was forcefully terminated by the
- * Temporal service.
- */
-public final class WorkflowTimedOutException extends WorkflowException {
-
- private final TimeoutType timeoutType;
-
- public WorkflowTimedOutException(
- WorkflowExecution execution, Optional workflowType, TimeoutType timeoutType) {
- super(timeoutType + " timeout type", execution, workflowType, null);
- this.timeoutType = timeoutType;
- }
-
- public TimeoutType getTimeoutType() {
- return timeoutType;
- }
-}
diff --git a/src/main/java/io/temporal/common/MethodRetry.java b/src/main/java/io/temporal/common/MethodRetry.java
index 3b80f049e9..9a7848abac 100644
--- a/src/main/java/io/temporal/common/MethodRetry.java
+++ b/src/main/java/io/temporal/common/MethodRetry.java
@@ -20,6 +20,7 @@
package io.temporal.common;
import io.temporal.activity.ActivityOptions;
+import io.temporal.failure.ApplicationFailure;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -64,12 +65,8 @@
long maximumIntervalSeconds() default 0;
/**
- * List of exceptions to retry. When matching an exact match is used. So adding
- * RuntimeException.class to this list is going to include only RuntimeException itself, not all
- * of its subclasses. The reason for such behaviour is to be able to support server side retries
- * without knowledge of Java exception hierarchy. {@link Error} and {@link
- * java.util.concurrent.CancellationException} are never retried, so they are not allowed in this
- * list.
+ * List of failure types to not retry. The failure type of an exception is its full class name. It
+ * can be also explicitly specified by throwing an {@link ApplicationFailure}
*/
- Class extends Throwable>[] doNotRetry() default {};
+ String[] doNotRetry() default {};
}
diff --git a/src/main/java/io/temporal/common/RetryOptions.java b/src/main/java/io/temporal/common/RetryOptions.java
index 8f3740147e..35716e657e 100644
--- a/src/main/java/io/temporal/common/RetryOptions.java
+++ b/src/main/java/io/temporal/common/RetryOptions.java
@@ -20,12 +20,12 @@
package io.temporal.common;
import com.google.common.base.Defaults;
-import io.temporal.workflow.ActivityFailureException;
-import io.temporal.workflow.ChildWorkflowFailureException;
+import io.temporal.failure.ActivityFailure;
+import io.temporal.failure.ApplicationFailure;
+import io.temporal.failure.CanceledFailure;
+import io.temporal.failure.ChildWorkflowFailure;
import java.time.Duration;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -108,7 +108,7 @@ public RetryOptions merge(RetryOptions o) {
}
@SafeVarargs
- public final RetryOptions addDoNotRetry(Class extends Throwable>... doNotRetry) {
+ public final RetryOptions addDoNotRetry(String... doNotRetry) {
if (doNotRetry == null) {
return this;
}
@@ -123,7 +123,7 @@ public final RetryOptions addDoNotRetry(Class extends Throwable>... doNotRetry
.setInitialInterval(getInitialInterval())
.setMaximumInterval(getMaximumInterval())
.setBackoffCoefficient(backoffCoefficient)
- .setDoNotRetry(merge(getDoNotRetry(), Arrays.asList(doNotRetry)));
+ .setDoNotRetry(merge(doNotRetry, getDoNotRetry()));
if (getMaximumAttempts() > 0) {
builder.setMaximumAttempts(getMaximumAttempts());
@@ -143,7 +143,7 @@ public static final class Builder {
private Duration maximumInterval;
- private List> doNotRetry;
+ private String[] doNotRetry;
private Builder(RetryOptions options) {
if (options == null) {
@@ -206,20 +206,16 @@ public Builder setMaximumInterval(Duration maximumInterval) {
}
/**
- * List of exceptions to retry. When matching an exact match is used. So adding
- * RuntimeException.class to this list is going to include only RuntimeException itself, not all
- * of its subclasses. The reason for such behaviour is to be able to support server side retries
- * without knowledge of Java exception hierarchy. When considering an exception type a cause of
- * {@link io.temporal.workflow.ActivityFailureException} and {@link
- * io.temporal.workflow.ChildWorkflowFailureException} is looked at.
+ * List of exceptions application failures types to retry. Application failures are converted to
+ * {@link ApplicationFailure#getType()}.
*
- *
{@link Error} and {@link java.util.concurrent.CancellationException} are never retried and
- * are not even passed to this filter.
+ *
{@link Error} and {@link CanceledFailure} are never retried and are not even passed to
+ * this filter.
*/
@SafeVarargs
- public final Builder setDoNotRetry(Class extends Throwable>... doNotRetry) {
+ public final Builder setDoNotRetry(String... doNotRetry) {
if (doNotRetry != null) {
- this.doNotRetry = Arrays.asList(doNotRetry);
+ this.doNotRetry = doNotRetry;
}
return this;
}
@@ -246,7 +242,7 @@ public RetryOptions validateBuildWithDefaults() {
backoff,
maximumAttempts,
maximumInterval,
- doNotRetry);
+ doNotRetry == null ? new String[0] : doNotRetry);
}
}
@@ -258,19 +254,19 @@ public RetryOptions validateBuildWithDefaults() {
private final Duration maximumInterval;
- private final List> doNotRetry;
+ private final String[] doNotRetry;
private RetryOptions(
Duration initialInterval,
double backoffCoefficient,
int maximumAttempts,
Duration maximumInterval,
- List> doNotRetry) {
+ String[] doNotRetry) {
this.initialInterval = initialInterval;
this.backoffCoefficient = backoffCoefficient;
this.maximumAttempts = maximumAttempts;
this.maximumInterval = maximumInterval;
- this.doNotRetry = doNotRetry != null ? Collections.unmodifiableList(doNotRetry) : null;
+ this.doNotRetry = doNotRetry;
}
public Duration getInitialInterval() {
@@ -293,7 +289,7 @@ public Duration getMaximumInterval() {
* @return null if not configured. When merging with annotation it makes a difference. null means
* use values from an annotation. Empty list means do not retry on anything.
*/
- public List> getDoNotRetry() {
+ public String[] getDoNotRetry() {
return doNotRetry;
}
@@ -313,7 +309,7 @@ public String toString() {
+ ", maximumInterval="
+ maximumInterval
+ ", doNotRetry="
- + doNotRetry
+ + Arrays.toString(doNotRetry)
+ '}';
}
@@ -326,13 +322,17 @@ public boolean equals(Object o) {
&& maximumAttempts == that.maximumAttempts
&& Objects.equals(initialInterval, that.initialInterval)
&& Objects.equals(maximumInterval, that.maximumInterval)
- && Objects.equals(doNotRetry, that.doNotRetry);
+ && Arrays.equals(doNotRetry, that.doNotRetry);
}
@Override
public int hashCode() {
return Objects.hash(
- initialInterval, backoffCoefficient, maximumAttempts, maximumInterval, doNotRetry);
+ initialInterval,
+ backoffCoefficient,
+ maximumAttempts,
+ maximumInterval,
+ Arrays.hashCode(doNotRetry));
}
private static G merge(G annotation, G options, Class type) {
@@ -349,29 +349,11 @@ private static Duration merge(long aSeconds, Duration o) {
return aSeconds == 0 ? null : Duration.ofSeconds(aSeconds);
}
- private static Class extends Throwable>[] merge(
- Class extends Throwable>[] a, List> o) {
- if (o != null) {
- @SuppressWarnings("unchecked")
- Class extends Throwable>[] result = new Class[o.size()];
- return o.toArray(result);
+ private static String[] merge(String[] fromAnnotation, String[] fromOptions) {
+ if (fromOptions != null) {
+ return fromOptions;
}
- return a.length == 0 ? null : a;
- }
-
- private Class extends Throwable>[] merge(
- List> o1, List> o2) {
- if (o2 != null) {
- @SuppressWarnings("unchecked")
- Class extends Throwable>[] result = new Class[o2.size()];
- return o2.toArray(result);
- }
- if (o1.size() > 0) {
- @SuppressWarnings("unchecked")
- Class extends Throwable>[] result = new Class[o1.size()];
- return o1.toArray(result);
- }
- return null;
+ return fromAnnotation;
}
public long calculateSleepTime(long attempt) {
@@ -386,12 +368,18 @@ public long calculateSleepTime(long attempt) {
public boolean shouldRethrow(
Throwable e, Optional expiration, long attempt, long elapsed, long sleepTime) {
- if (e instanceof ActivityFailureException || e instanceof ChildWorkflowFailureException) {
+ String type;
+ if (e instanceof ActivityFailure || e instanceof ChildWorkflowFailure) {
e = e.getCause();
}
+ if (e instanceof ApplicationFailure) {
+ type = ((ApplicationFailure) e).getType();
+ } else {
+ type = e.getClass().getName();
+ }
if (doNotRetry != null) {
- for (Class extends Throwable> doNotRetry : doNotRetry) {
- if (doNotRetry.equals(e.getClass())) {
+ for (String doNotRetry : doNotRetry) {
+ if (doNotRetry.equals(type)) {
return true;
}
}
diff --git a/src/main/java/io/temporal/common/converter/ByteArrayPayloadConverter.java b/src/main/java/io/temporal/common/converter/ByteArrayPayloadConverter.java
new file mode 100644
index 0000000000..599426eaee
--- /dev/null
+++ b/src/main/java/io/temporal/common/converter/ByteArrayPayloadConverter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
+ *
+ * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Modifications copyright (C) 2017 Uber Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not
+ * use this file except in compliance with the License. A copy of the License is
+ * located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package io.temporal.common.converter;
+
+import com.google.protobuf.ByteString;
+import io.temporal.proto.common.Payload;
+import java.lang.reflect.Type;
+import java.util.Optional;
+
+public final class ByteArrayPayloadConverter implements PayloadConverter {
+ @Override
+ public String getEncodingType() {
+ return EncodingKeys.METADATA_ENCODING_RAW_NAME;
+ }
+
+ @Override
+ public Optional toData(Object value) throws DataConverterException {
+ if (value instanceof byte[]) {
+ return Optional.of(
+ Payload.newBuilder()
+ .putMetadata(EncodingKeys.METADATA_ENCODING_KEY, EncodingKeys.METADATA_ENCODING_RAW)
+ .setData(ByteString.copyFrom((byte[]) value))
+ .build());
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T fromData(Payload content, Class valueClass, Type valueType)
+ throws DataConverterException {
+ ByteString data = content.getData();
+ if (valueClass != byte[].class) {
+ throw new IllegalArgumentException(
+ "Raw encoding can be deserialized only to a byte array. valueClass="
+ + valueClass.getName());
+ }
+ return (T) data.toByteArray();
+ }
+}
diff --git a/src/main/java/io/temporal/common/converter/CustomThrowableTypeAdapter.java b/src/main/java/io/temporal/common/converter/CustomThrowableTypeAdapter.java
deleted file mode 100644
index f83b932da1..0000000000
--- a/src/main/java/io/temporal/common/converter/CustomThrowableTypeAdapter.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
- *
- * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Modifications copyright (C) 2017 Uber Technologies, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"). You may not
- * use this file except in compliance with the License. A copy of the License is
- * located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package io.temporal.common.converter;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.TypeAdapter;
-import com.google.gson.TypeAdapterFactory;
-import com.google.gson.reflect.TypeToken;
-import com.google.gson.stream.JsonReader;
-import com.google.gson.stream.JsonWriter;
-import io.temporal.internal.common.DataConverterUtils;
-import java.io.IOException;
-import java.util.regex.Pattern;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-final class CustomThrowableTypeAdapter extends TypeAdapter {
- private static final Logger log = LoggerFactory.getLogger(CustomThrowableTypeAdapter.class);
-
- /** Used to parse a stack trace line. */
- private static final String TRACE_ELEMENT_REGEXP =
- "((?.*)\\.(?.*))\\(((?.*?)(:(?\\d+))?)\\)";
-
- private static final Pattern TRACE_ELEMENT_PATTERN = Pattern.compile(TRACE_ELEMENT_REGEXP);
-
- private final Gson gson;
- private final TypeAdapterFactory skipPast;
-
- CustomThrowableTypeAdapter(Gson gson, TypeAdapterFactory skipPast) {
- this.gson = gson;
- this.skipPast = skipPast;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void write(JsonWriter jsonWriter, T throwable) throws IOException {
- // We want to serialize the throwable and its cause separately, so that if the throwable
- // is serializable but the cause is not, we can still serialize them correctly (i.e. we
- // serialize the throwable correctly and convert the cause to a data converter exception).
- // If existing cause is not detached due to security policy then null is returned.
- Throwable cause = DataConverterUtils.detachCause(throwable);
-
- JsonObject object;
- try {
- TypeAdapter exceptionTypeAdapter =
- gson.getDelegateAdapter(skipPast, TypeToken.get(throwable.getClass()));
- object = exceptionTypeAdapter.toJsonTree(throwable).getAsJsonObject();
- object.add("class", new JsonPrimitive(throwable.getClass().getName()));
- String stackTrace = DataConverterUtils.serializeStackTrace(throwable);
- object.add("stackTrace", new JsonPrimitive(stackTrace));
- } catch (Throwable e) {
- // In case a throwable is not serializable, we will convert it to a data converter exception.
- // The cause of the data converter exception will indicate why the serialization failed. On
- // the other hand, if the non-serializable throwable contains a cause, we will add it to the
- // suppressed exceptions list.
- DataConverterException ee =
- new DataConverterException("Failure serializing exception: " + throwable.toString(), e);
- if (cause != null) {
- ee.addSuppressed(cause);
- cause = null;
- }
-
- TypeAdapter exceptionTypeAdapter =
- new CustomThrowableTypeAdapter<>(gson, skipPast);
- object = exceptionTypeAdapter.toJsonTree(ee).getAsJsonObject();
- }
-
- if (cause != null) {
- TypeAdapter causeTypeAdapter = new CustomThrowableTypeAdapter<>(gson, skipPast);
- try {
- object.add("cause", causeTypeAdapter.toJsonTree(cause));
- } catch (Throwable e) {
- DataConverterException ee =
- new DataConverterException("Failure serializing exception: " + cause.toString(), e);
- ee.setStackTrace(cause.getStackTrace());
- object.add("cause", causeTypeAdapter.toJsonTree(ee));
- }
- }
-
- TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class);
- elementAdapter.write(jsonWriter, object);
- }
-
- @Override
- public T read(JsonReader jsonReader) throws IOException {
- TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class);
- JsonObject object = elementAdapter.read(jsonReader).getAsJsonObject();
- JsonElement classElement = object.get("class");
- if (classElement != null) {
- String className = classElement.getAsString();
- Class> classType;
- try {
- classType = Class.forName(className);
- } catch (ClassNotFoundException e) {
- throw new IOException("Cannot deserialize " + className + " exception", e);
- }
- if (!Throwable.class.isAssignableFrom(classType)) {
- throw new IOException("Expected type that extends Throwable: " + className);
- }
-
- StackTraceElement[] stackTrace = parseStackTrace(object);
- // This is important. Initially I tried configuring ExclusionStrategy to not
- // deserialize the stackTrace field.
- // But it left it null, which caused Thread.setStackTrace implementation to become
- // silent noop.
- object.add("stackTrace", new JsonArray());
- TypeAdapter exceptionTypeAdapter =
- gson.getDelegateAdapter(skipPast, TypeToken.get(classType));
- Throwable result = (Throwable) exceptionTypeAdapter.fromJsonTree(object);
- result.setStackTrace(stackTrace);
- @SuppressWarnings("unchecked")
- T typedResult = (T) result;
- return typedResult;
- }
- throw new IOException();
- }
-
- private StackTraceElement[] parseStackTrace(JsonObject object) {
- JsonElement jsonStackTrace = object.get("stackTrace");
- if (jsonStackTrace == null) {
- return new StackTraceElement[0];
- }
- String stackTrace = jsonStackTrace.getAsString();
- return DataConverterUtils.parseStackTrace(stackTrace);
- }
-}
diff --git a/src/main/java/io/temporal/common/converter/DataConverter.java b/src/main/java/io/temporal/common/converter/DataConverter.java
index ec894b880f..b3d1b1f8f6 100644
--- a/src/main/java/io/temporal/common/converter/DataConverter.java
+++ b/src/main/java/io/temporal/common/converter/DataConverter.java
@@ -19,6 +19,7 @@
package io.temporal.common.converter;
+import io.temporal.proto.common.Payload;
import io.temporal.proto.common.Payloads;
import java.lang.reflect.Type;
import java.util.Optional;
@@ -31,7 +32,13 @@
*/
public interface DataConverter {
- PayloadConverter getPayloadConverter();
+ static DataConverter getDefaultInstance() {
+ return DefaultDataConverter.getDefaultInstance();
+ }
+
+ Optional toPayload(T value);
+
+ T fromPayload(Payload payload, Class valueClass, Type valueType);
/**
* Implements conversion of a list of values.
@@ -41,7 +48,7 @@ public interface DataConverter {
* @throws DataConverterException if conversion of the value passed as parameter failed for any
* reason.
*/
- Optional toData(Object... values) throws DataConverterException;
+ Optional toPayloads(Object... values) throws DataConverterException;
/**
* Implements conversion of a single value.
@@ -53,7 +60,7 @@ public interface DataConverter {
* @throws DataConverterException if conversion of the data passed as parameter failed for any
* reason.
*/
- T fromData(Optional content, Class parameterType, Type genericParameterType)
+ T fromPayloads(Optional content, Class parameterType, Type genericParameterType)
throws DataConverterException;
/**
@@ -67,7 +74,7 @@ T fromData(Optional content, Class parameterType, Type genericP
* @throws DataConverterException if conversion of the data passed as parameter failed for any
* reason.
*/
- public Object[] fromDataArray(
+ Object[] arrayFromPayloads(
Optional content, Class>[] parameterTypes, Type[] genericParameterTypes)
throws DataConverterException;
}
diff --git a/src/main/java/io/temporal/common/converter/DataConverterException.java b/src/main/java/io/temporal/common/converter/DataConverterException.java
index 454bc3c570..c1eee624f9 100644
--- a/src/main/java/io/temporal/common/converter/DataConverterException.java
+++ b/src/main/java/io/temporal/common/converter/DataConverterException.java
@@ -60,6 +60,10 @@ public DataConverterException(String message, Optional content, Type[]
super(toMessage(message, content, valueTypes));
}
+ public DataConverterException(Payload payload, Class valueClass, Throwable e) {
+ super(toMessage(e.getMessage(), payload, new Type[] {valueClass}), e);
+ }
+
private static String toMessage(String message, Optional content, Type[] valueTypes) {
if (content == null && valueTypes == null || valueTypes.length == 0) {
return message;
diff --git a/src/main/java/io/temporal/common/converter/GsonJsonDataConverter.java b/src/main/java/io/temporal/common/converter/DefaultDataConverter.java
similarity index 53%
rename from src/main/java/io/temporal/common/converter/GsonJsonDataConverter.java
rename to src/main/java/io/temporal/common/converter/DefaultDataConverter.java
index 7b2b07cb59..d394e9491b 100644
--- a/src/main/java/io/temporal/common/converter/GsonJsonDataConverter.java
+++ b/src/main/java/io/temporal/common/converter/DefaultDataConverter.java
@@ -19,40 +19,92 @@
package io.temporal.common.converter;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.base.Defaults;
import io.temporal.proto.common.Payload;
import io.temporal.proto.common.Payloads;
import java.lang.reflect.Type;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
/**
- * Implements conversion through GSON JSON processor. To extend use {@link
- * #GsonJsonDataConverter(PayloadConverter)} constructor.
+ * DataConverter that delegates conversion to type specific PayloadConverter instance.
*
* @author fateev
*/
-public final class GsonJsonDataConverter implements DataConverter {
+public class DefaultDataConverter implements DataConverter {
- private static final DataConverter INSTANCE = new GsonJsonDataConverter();
+ private static final AtomicReference defaultDataConverterInstance =
+ new AtomicReference<>(
+ // Order is important as the first converter that can convert the payload is used
+ new DefaultDataConverter(
+ new NullPayloadConverter(),
+ new ByteArrayPayloadConverter(),
+ new JacksonJsonPayloadConverter()));
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
- private final PayloadConverter converter;
+ private final Map converterMap = new ConcurrentHashMap<>();
+ private final List converters = new ArrayList<>();
- public static DataConverter getInstance() {
- return INSTANCE;
+ static DataConverter getDefaultInstance() {
+ return defaultDataConverterInstance.get();
}
- private GsonJsonDataConverter() {
- this(GsonJsonPayloadConverter.getInstance());
+ /**
+ * Override the global data converter default. Consider overriding data converter per client
+ * instance (using {@link
+ * io.temporal.client.WorkflowClientOptions.Builder#setDataConverter(DataConverter)} to avoid
+ * potential conflicts.
+ *
+ * @param converter
+ */
+ public static void setDefaultDataConverter(DataConverter converter) {
+ defaultDataConverterInstance.set(converter);
}
- public GsonJsonDataConverter(PayloadConverter converter) {
- this.converter = converter;
+ /**
+ * Creates instance from ordered array of converters. When converting an object to payload the
+ * array of converters is iterated from the beginning until one of the converters succesfully
+ * converts the value.
+ */
+ public DefaultDataConverter(PayloadConverter... converters) {
+ for (PayloadConverter converter : converters) {
+ this.converters.add(converter);
+ this.converterMap.put(converter.getEncodingType(), converter);
+ }
}
@Override
- public PayloadConverter getPayloadConverter() {
- return converter;
+ public Optional toPayload(T value) {
+ for (PayloadConverter converter : converters) {
+ Optional result = converter.toData(value);
+ if (result.isPresent()) {
+ return result;
+ }
+ }
+ throw new IllegalArgumentException("Failure serializing " + value);
+ }
+
+ @Override
+ public T fromPayload(Payload payload, Class valueClass, Type valueType) {
+ try {
+ String encoding =
+ payload.getMetadataOrThrow(EncodingKeys.METADATA_ENCODING_KEY).toString(UTF_8);
+ PayloadConverter converter = converterMap.get(encoding);
+ if (converter == null) {
+ throw new IllegalArgumentException("Unknown encoding: " + encoding);
+ }
+ return converter.fromData(payload, valueClass, valueType);
+ } catch (DataConverterException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new DataConverterException(payload, valueClass, e);
+ }
}
/**
@@ -63,14 +115,14 @@ public PayloadConverter getPayloadConverter() {
* @return serialized values
*/
@Override
- public Optional toData(Object... values) throws DataConverterException {
+ public Optional toPayloads(Object... values) throws DataConverterException {
if (values == null || values.length == 0) {
return Optional.empty();
}
try {
Payloads.Builder result = Payloads.newBuilder();
for (Object value : values) {
- Optional payload = converter.toData(value);
+ Optional payload = toPayload(value);
if (payload.isPresent()) {
result.addPayloads(payload.get());
} else {
@@ -86,7 +138,7 @@ public Optional toData(Object... values) throws DataConverterException
}
@Override
- public T fromData(Optional content, Class valueClass, Type valueType)
+ public T fromPayloads(Optional content, Class valueClass, Type valueType)
throws DataConverterException {
if (!content.isPresent()) {
return null;
@@ -99,11 +151,11 @@ public T fromData(Optional content, Class valueClass, Type valu
throw new DataConverterException(
"Found multiple payloads while a single one expected", content, valueType);
}
- return converter.fromData(c.getPayloads(0), valueClass, valueType);
+ return fromPayload(c.getPayloads(0), valueClass, valueType);
}
@Override
- public Object[] fromDataArray(
+ public Object[] arrayFromPayloads(
Optional content, Class>[] parameterTypes, Type[] valueTypes)
throws DataConverterException {
try {
@@ -132,7 +184,7 @@ public Object[] fromDataArray(
if (i >= count) {
result[i] = Defaults.defaultValue((Class>) vt);
} else {
- result[i] = converter.fromData(c.getPayloads(i), pt, vt);
+ result[i] = fromPayload(c.getPayloads(i), pt, vt);
}
}
return result;
diff --git a/src/main/java/io/temporal/common/converter/EncodedValue.java b/src/main/java/io/temporal/common/converter/EncodedValue.java
new file mode 100644
index 0000000000..3e6bfbcc31
--- /dev/null
+++ b/src/main/java/io/temporal/common/converter/EncodedValue.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
+ *
+ * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Modifications copyright (C) 2017 Uber Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not
+ * use this file except in compliance with the License. A copy of the License is
+ * located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package io.temporal.common.converter;
+
+import io.temporal.proto.common.Payloads;
+import java.lang.reflect.Type;
+import java.util.Objects;
+import java.util.Optional;
+
+public final class EncodedValue implements Value {
+ private Optional payloads;
+ private DataConverter converter;
+ private final Optional