diff --git a/temporal-sdk/build.gradle b/temporal-sdk/build.gradle index 8f9b55f31a..1f0d4f28cd 100644 --- a/temporal-sdk/build.gradle +++ b/temporal-sdk/build.gradle @@ -12,6 +12,13 @@ dependencies { if (!JavaVersion.current().isJava8()) { implementation 'javax.annotation:javax.annotation-api:1.3.2' } + // compileOnly and testImplementation because this dependency is needed only to work with json format of history + // which shouldn't be needed for any production usage of temporal-sdk. + // It's useful only for unit tests and debugging. + // For these use-cases Temporal users can add this dep in the classpath temporary or permanently themselves. + compileOnly group: 'com.jayway.jsonpath', name: 'json-path', version: '2.6.0' + testImplementation group: 'com.jayway.jsonpath', name: 'json-path', version: '2.6.0' + testImplementation project(':temporal-testing-junit4') testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: "${logbackVersion}" testImplementation group: 'junit', name: 'junit', version: '4.13.2' diff --git a/temporal-sdk/src/main/java/io/temporal/internal/common/HistoryJsonUtils.java b/temporal-sdk/src/main/java/io/temporal/internal/common/HistoryJsonUtils.java new file mode 100644 index 0000000000..91b7b11904 --- /dev/null +++ b/temporal-sdk/src/main/java/io/temporal/internal/common/HistoryJsonUtils.java @@ -0,0 +1,89 @@ +/* + * 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.internal.common; + +import com.google.common.base.CaseFormat; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; + +/** + * Helper methods supporting transformation of History's "Proto Json" compatible format, which is + * supported by {@link com.google.protobuf.util.JsonFormat} to the format of Temporal history + * supported by tctl and back. + * + * @see + * Related commit to Go Proto module + */ +class HistoryJsonUtils { + private static final JsonPath EVENT_TYPE_PATH = JsonPath.compile("$.events.*.eventType"); + private static final JsonPath TASK_QUEUE_KIND_PATH = + JsonPath.compile("$.events.*.*.taskQueue.kind"); + private static final String EVENT_TYPE_PREFIX = "EVENT_TYPE_"; + private static final String TASK_QUEUE_KIND_PREFIX = "TASK_QUEUE_KIND_"; + + public static String protoJsonToHistoryFormatJson(String protoJson) { + DocumentContext parsed = JsonPath.parse(protoJson); + parsed.map( + EVENT_TYPE_PATH, + (currentValue, configuration) -> + enumProtoToHistory((String) currentValue, EVENT_TYPE_PREFIX)); + parsed.map( + TASK_QUEUE_KIND_PATH, + (currentValue, configuration) -> + enumProtoToHistory((String) currentValue, TASK_QUEUE_KIND_PREFIX)); + return parsed.jsonString(); + } + + public static String historyFormatJsonToProtoJson(String historyFormatJson) { + DocumentContext parsed = JsonPath.parse(historyFormatJson); + parsed.map( + EVENT_TYPE_PATH, + (currentValue, configuration) -> + enumHistoryToProto((String) currentValue, EVENT_TYPE_PREFIX)); + parsed.map( + TASK_QUEUE_KIND_PATH, + (currentValue, configuration) -> + enumHistoryToProto((String) currentValue, TASK_QUEUE_KIND_PREFIX)); + return parsed.jsonString(); + } + + private static String enumProtoToHistory(String protoEnumValue, String prefix) { + if (!protoEnumValue.startsWith(prefix)) { + throw new IllegalArgumentException("protoEnumValue should start with " + prefix + " prefix"); + } + protoEnumValue = protoEnumValue.substring(prefix.length()); + return screamingCaseEventTypeToCamelCase(protoEnumValue); + } + + private static String enumHistoryToProto(String historyEnumValue, String prefix) { + return prefix + camelCaseToScreamingCase(historyEnumValue); + } + + // https://github.com/temporalio/gogo-protobuf/commit/b38fb010909b8f81e2e600dc6f04925fc71d6a5e + private static String camelCaseToScreamingCase(String camel) { + return CaseFormat.UPPER_CAMEL.converterTo(CaseFormat.UPPER_UNDERSCORE).convert(camel); + } + + // https://github.com/temporalio/gogo-protobuf/commit/b38fb010909b8f81e2e600dc6f04925fc71d6a5e + private static String screamingCaseEventTypeToCamelCase(String screaming) { + return CaseFormat.UPPER_UNDERSCORE.converterTo(CaseFormat.UPPER_CAMEL).convert(screaming); + } +} diff --git a/temporal-sdk/src/main/java/io/temporal/internal/common/WorkflowExecutionHistory.java b/temporal-sdk/src/main/java/io/temporal/internal/common/WorkflowExecutionHistory.java index 6897069d2e..6bc8c173f6 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/common/WorkflowExecutionHistory.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/common/WorkflowExecutionHistory.java @@ -38,10 +38,12 @@ public WorkflowExecutionHistory(History history) { } public static WorkflowExecutionHistory fromJson(String serialized) { + String protoJson = HistoryJsonUtils.historyFormatJsonToProtoJson(serialized); + JsonFormat.Parser parser = JsonFormat.parser(); History.Builder historyBuilder = History.newBuilder(); try { - parser.merge(serialized, historyBuilder); + parser.merge(protoJson, historyBuilder); } catch (InvalidProtocolBufferException e) { throw new DataConverterException(e); } @@ -68,16 +70,13 @@ private static void checkHistory(History history) { public String toJson() { JsonFormat.Printer printer = JsonFormat.printer(); try { - return printer.print(history); + String protoJson = printer.print(history); + return HistoryJsonUtils.protoJsonToHistoryFormatJson(protoJson); } catch (InvalidProtocolBufferException e) { throw new DataConverterException(e); } } - public String toPrettyPrintedJson() { - return toJson(); - } - public WorkflowExecution getWorkflowExecution() { return WorkflowExecution.newBuilder() .setWorkflowId("workflow_id_in_replay") diff --git a/temporal-sdk/src/test/resources/testAsyncActivityRetryHistory.json b/temporal-sdk/src/test/resources/testAsyncActivityRetryHistory.json index 86cf380ee0..9b2664ae68 100755 --- a/temporal-sdk/src/test/resources/testAsyncActivityRetryHistory.json +++ b/temporal-sdk/src/test/resources/testAsyncActivityRetryHistory.json @@ -2,7 +2,7 @@ "events": [{ "eventId": "1", "eventTime": "2020-07-14T15:04:02Z", - "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "eventType": "WorkflowExecutionStarted", "version": "-24", "taskId": "1051005", "workflowExecutionStartedEventAttributes": { @@ -11,7 +11,7 @@ }, "taskQueue": { "name": "WorkflowTest-testAsyncActivityRetry-61724a56-8299-42ec-a98d-f180000e8784", - "kind": "TASK_QUEUE_KIND_NORMAL" + "kind": "Normal" }, "input": { "payloads": [{ @@ -32,7 +32,7 @@ }, { "eventId": "2", "eventTime": "2020-07-14T15:04:02Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "eventType": "WorkflowTaskScheduled", "version": "-24", "taskId": "1051006", "workflowTaskScheduledEventAttributes": { @@ -44,7 +44,7 @@ }, { "eventId": "3", "eventTime": "2020-07-14T15:04:02Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "eventType": "WorkflowTaskStarted", "version": "-24", "taskId": "1051011", "workflowTaskStartedEventAttributes": { @@ -55,7 +55,7 @@ }, { "eventId": "4", "eventTime": "2020-07-14T15:04:02Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "eventType": "WorkflowTaskCompleted", "version": "-24", "taskId": "1051014", "workflowTaskCompletedEventAttributes": { @@ -66,7 +66,7 @@ }, { "eventId": "5", "eventTime": "2020-07-14T15:04:02Z", - "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "eventType": "ActivityTaskScheduled", "version": "-24", "taskId": "1051015", "activityTaskScheduledEventAttributes": { @@ -92,7 +92,7 @@ }, { "eventId": "6", "eventTime": "2020-07-14T15:04:04Z", - "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "eventType": "ActivityTaskStarted", "version": "-24", "taskId": "1051026", "activityTaskStartedEventAttributes": { @@ -112,7 +112,7 @@ }, { "eventId": "7", "eventTime": "2020-07-14T15:04:04Z", - "eventType": "EVENT_TYPE_ACTIVITY_TASK_FAILED", + "eventType": "ActivityTaskFailed", "version": "-24", "taskId": "1051027", "activityTaskFailedEventAttributes": { @@ -132,7 +132,7 @@ }, { "eventId": "8", "eventTime": "2020-07-14T15:04:04Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "eventType": "WorkflowTaskScheduled", "version": "-24", "taskId": "1051030", "workflowTaskScheduledEventAttributes": { @@ -144,7 +144,7 @@ }, { "eventId": "9", "eventTime": "2020-07-14T15:04:04Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "eventType": "WorkflowTaskStarted", "version": "-24", "taskId": "1051034", "workflowTaskStartedEventAttributes": { @@ -155,7 +155,7 @@ }, { "eventId": "10", "eventTime": "2020-07-14T15:04:04Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "eventType": "WorkflowTaskCompleted", "version": "-24", "taskId": "1051037", "workflowTaskCompletedEventAttributes": { @@ -166,7 +166,7 @@ }, { "eventId": "11", "eventTime": "2020-07-14T15:04:04Z", - "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_FAILED", + "eventType": "WorkflowExecutionFailed", "version": "-24", "taskId": "1051038", "workflowExecutionFailedEventAttributes": { diff --git a/temporal-sdk/src/test/resources/testChildWorkflowRetryHistory.json b/temporal-sdk/src/test/resources/testChildWorkflowRetryHistory.json index 5654b8b729..c7816c7440 100755 --- a/temporal-sdk/src/test/resources/testChildWorkflowRetryHistory.json +++ b/temporal-sdk/src/test/resources/testChildWorkflowRetryHistory.json @@ -2,7 +2,7 @@ "events": [{ "eventId": "1", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "eventType": "WorkflowExecutionStarted", "version": "-24", "taskId": "1051760", "workflowExecutionStartedEventAttributes": { @@ -11,7 +11,7 @@ }, "taskQueue": { "name": "WorkflowTest-testChildWorkflowRetry-c5c598cd-05d6-4790-b43e-ebf149aee65b", - "kind": "TASK_QUEUE_KIND_NORMAL" + "kind": "Normal" }, "input": { "payloads": [{ @@ -32,7 +32,7 @@ }, { "eventId": "2", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "eventType": "WorkflowTaskScheduled", "version": "-24", "taskId": "1051761", "workflowTaskScheduledEventAttributes": { @@ -44,7 +44,7 @@ }, { "eventId": "3", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "eventType": "WorkflowTaskStarted", "version": "-24", "taskId": "1051766", "workflowTaskStartedEventAttributes": { @@ -55,7 +55,7 @@ }, { "eventId": "4", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "eventType": "WorkflowTaskCompleted", "version": "-24", "taskId": "1051769", "workflowTaskCompletedEventAttributes": { @@ -66,7 +66,7 @@ }, { "eventId": "5", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED", + "eventType": "StartChildWorkflowExecutionInitiated", "version": "-24", "taskId": "1051770", "startChildWorkflowExecutionInitiatedEventAttributes": { @@ -106,7 +106,7 @@ }, { "eventId": "6", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED", + "eventType": "ChildWorkflowExecutionStarted", "version": "-24", "taskId": "1051773", "childWorkflowExecutionStartedEventAttributes": { @@ -122,7 +122,7 @@ }, { "eventId": "7", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "eventType": "WorkflowTaskScheduled", "version": "-24", "taskId": "1051775", "workflowTaskScheduledEventAttributes": { @@ -134,7 +134,7 @@ }, { "eventId": "8", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "eventType": "WorkflowTaskStarted", "version": "-24", "taskId": "1051779", "workflowTaskStartedEventAttributes": { @@ -145,7 +145,7 @@ }, { "eventId": "9", "eventTime": "2020-07-14T15:05:35Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "eventType": "WorkflowTaskCompleted", "version": "-24", "taskId": "1051782", "workflowTaskCompletedEventAttributes": { @@ -156,7 +156,7 @@ }, { "eventId": "10", "eventTime": "2020-07-14T15:05:37Z", - "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_FAILED", + "eventType": "ChildWorkflowExecutionFailed", "version": "-24", "taskId": "1051784", "childWorkflowExecutionFailedEventAttributes": { @@ -182,7 +182,7 @@ }, { "eventId": "11", "eventTime": "2020-07-14T15:05:37Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "eventType": "WorkflowTaskScheduled", "version": "-24", "taskId": "1051786", "workflowTaskScheduledEventAttributes": { @@ -194,7 +194,7 @@ }, { "eventId": "12", "eventTime": "2020-07-14T15:05:37Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "eventType": "WorkflowTaskStarted", "version": "-24", "taskId": "1051790", "workflowTaskStartedEventAttributes": { @@ -205,7 +205,7 @@ }, { "eventId": "13", "eventTime": "2020-07-14T15:05:37Z", - "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "eventType": "WorkflowTaskCompleted", "version": "-24", "taskId": "1051793", "workflowTaskCompletedEventAttributes": { @@ -216,7 +216,7 @@ }, { "eventId": "14", "eventTime": "2020-07-14T15:05:37Z", - "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_FAILED", + "eventType": "WorkflowExecutionFailed", "version": "-24", "taskId": "1051794", "workflowExecutionFailedEventAttributes": { diff --git a/temporal-testing-junit4/src/main/java/io/temporal/testing/internal/SDKTestWorkflowRule.java b/temporal-testing-junit4/src/main/java/io/temporal/testing/internal/SDKTestWorkflowRule.java index edbb02e276..795c058bfd 100644 --- a/temporal-testing-junit4/src/main/java/io/temporal/testing/internal/SDKTestWorkflowRule.java +++ b/temporal-testing-junit4/src/main/java/io/temporal/testing/internal/SDKTestWorkflowRule.java @@ -317,7 +317,7 @@ public static void regenerateHistoryForReplay( GetWorkflowExecutionHistoryResponse response = service.blockingStub().getWorkflowExecutionHistory(request); WorkflowExecutionHistory history = new WorkflowExecutionHistory(response.getHistory()); - String json = history.toPrettyPrintedJson(); + String json = history.toJson(); String projectPath = System.getProperty("user.dir"); String resourceFile = projectPath + "/src/test/resources/" + fileName + ".json"; File file = new File(resourceFile);