From 4e3d7050713686c3db16cf5c7e25a730f228df25 Mon Sep 17 00:00:00 2001
From: leo <long_187@126.com>
Date: Fri, 5 Mar 2021 10:17:38 +0800
Subject: [PATCH] optimize: Saga SpringBeanServiceInvoker support switch json
 parser (#3513)

---
 .../impl/DefaultStateMachineConfig.java       |   1 +
 .../impl/SpringBeanServiceInvoker.java        |  22 ++-
 .../saga/statelang/parser/JsonParser.java     |   9 ++
 .../statelang/parser/impl/FastjsonParser.java |  24 ++-
 .../parser/impl/JacksonJsonParser.java        |  20 ++-
 .../saga/engine/StateMachineAsyncTests.java   |   2 +-
 .../seata/saga/engine/StateMachineTests.java  |  33 +++-
 .../saga/engine/db/StateMachineDBTests.java   |  32 ++++
 .../StateMachineAsyncDBMockServerTests.java   |   4 +-
 .../StateMachineDBMockServerTests.java        |   4 +-
 .../simple_statelang_with_complex_params.json |   2 +-
 ...statelang_with_complex_params_jackson.json | 141 ++++++++++++++++++
 12 files changed, 278 insertions(+), 16 deletions(-)
 create mode 100644 test/src/test/resources/saga/statelang/simple_statelang_with_complex_params_jackson.json

diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java
index 5ec5299ee..6e47cddc5 100644
--- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java
+++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java
@@ -208,6 +208,7 @@ public class DefaultStateMachineConfig implements StateMachineConfig, Applicatio
             SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker();
             springBeanServiceInvoker.setApplicationContext(getApplicationContext());
             springBeanServiceInvoker.setThreadPoolExecutor(threadPoolExecutor);
+            springBeanServiceInvoker.setSagaJsonParser(getSagaJsonParser());
             this.serviceInvokerManager.putServiceInvoker(DomainConstants.SERVICE_TYPE_SPRING_BEAN,
                 springBeanServiceInvoker);
         }
diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/impl/SpringBeanServiceInvoker.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/impl/SpringBeanServiceInvoker.java
index af41a7ebc..9f4908a8b 100644
--- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/impl/SpringBeanServiceInvoker.java
+++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/impl/SpringBeanServiceInvoker.java
@@ -27,9 +27,6 @@ import java.util.Map;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.Feature;
-
 import io.seata.common.exception.FrameworkErrorCode;
 import io.seata.common.util.CollectionUtils;
 import io.seata.saga.engine.exception.EngineExecutionException;
@@ -39,6 +36,8 @@ import io.seata.saga.engine.utils.ExceptionUtils;
 import io.seata.saga.statelang.domain.ServiceTaskState;
 import io.seata.saga.statelang.domain.TaskState.Retry;
 import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl;
+import io.seata.saga.statelang.parser.JsonParser;
+import io.seata.saga.statelang.parser.JsonParserFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -56,6 +55,7 @@ public class SpringBeanServiceInvoker implements ServiceInvoker, ApplicationCont
 
     private ApplicationContext applicationContext;
     private ThreadPoolExecutor threadPoolExecutor;
+    private String sagaJsonParser;
 
     @Override
     public Object invoke(ServiceTaskState serviceTaskState, Object... input) throws Throwable {
@@ -296,8 +296,12 @@ public class SpringBeanServiceInvoker implements ServiceInvoker, ApplicationCont
         } else if (isPrimitive(paramType)) {
             return value;
         } else {
-            String jsonValue = JSON.toJSONString(value);
-            return JSON.parseObject(jsonValue, paramType, Feature.SupportAutoType);
+            JsonParser jsonParser = JsonParserFactory.getJsonParser(getSagaJsonParser());
+            if (jsonParser == null) {
+                throw new RuntimeException("Cannot get JsonParser by name : " + getSagaJsonParser());
+            }
+            String jsonValue = jsonParser.toJsonString(value, true, false);
+            return jsonParser.parse(jsonValue, paramType, false);
         }
     }
 
@@ -344,4 +348,12 @@ public class SpringBeanServiceInvoker implements ServiceInvoker, ApplicationCont
             return null;
         }
     }
+
+    public String getSagaJsonParser() {
+        return sagaJsonParser;
+    }
+
+    public void setSagaJsonParser(String sagaJsonParser) {
+        this.sagaJsonParser = sagaJsonParser;
+    }
 }
\ No newline at end of file
diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParser.java
index 37fef5c25..3d956d131 100644
--- a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParser.java
+++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParser.java
@@ -39,6 +39,15 @@ public interface JsonParser {
      */
     String toJsonString(Object o, boolean prettyPrint);
 
+    /**
+     * Object to Json string
+     * @param o
+     * @param ignoreAutoType
+     * @param prettyPrint
+     * @return
+     */
+    String toJsonString(Object o, boolean ignoreAutoType, boolean prettyPrint);
+
     /**
      * parse json string to Object
      *
diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FastjsonParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FastjsonParser.java
index 04efa8eed..b7ccfb35a 100644
--- a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FastjsonParser.java
+++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FastjsonParser.java
@@ -40,6 +40,11 @@ public class FastjsonParser implements JsonParser {
         SerializerFeature.WriteClassName,
         SerializerFeature.PrettyFormat };
 
+    private static final SerializerFeature[] FEATURES_PRETTY = new SerializerFeature[] {
+        SerializerFeature.DisableCircularReferenceDetect,
+        SerializerFeature.WriteDateUseDateFormat,
+        SerializerFeature.PrettyFormat };
+
     public static final String NAME = "fastjson";
 
     @Override
@@ -49,11 +54,26 @@ public class FastjsonParser implements JsonParser {
 
     @Override
     public String toJsonString(Object o, boolean prettyPrint) {
+        return toJsonString(o, false, prettyPrint);
+    }
+
+    @Override
+    public String toJsonString(Object o, boolean ignoreAutoType, boolean prettyPrint) {
         if (prettyPrint) {
-            return JSON.toJSONString(o, SERIALIZER_FEATURES_PRETTY);
+            if (ignoreAutoType) {
+                return JSON.toJSONString(o, FEATURES_PRETTY);
+            }
+            else {
+                return JSON.toJSONString(o, SERIALIZER_FEATURES_PRETTY);
+            }
         }
         else {
-            return JSON.toJSONString(o, SERIALIZER_FEATURES);
+            if (ignoreAutoType) {
+                return JSON.toJSONString(o);
+            }
+            else {
+                return JSON.toJSONString(o, SERIALIZER_FEATURES);
+            }
         }
     }
 
diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/JacksonJsonParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/JacksonJsonParser.java
index 6d84558dd..3407df89f 100644
--- a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/JacksonJsonParser.java
+++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/JacksonJsonParser.java
@@ -61,15 +61,31 @@ public class JacksonJsonParser implements JsonParser {
 
     @Override
     public String toJsonString(Object o, boolean prettyPrint) {
+        return toJsonString(o, false, prettyPrint);
+    }
+
+    @Override
+    public String toJsonString(Object o, boolean ignoreAutoType, boolean prettyPrint) {
         try {
             if (o instanceof List && ((List) o).isEmpty()) {
                 return "[]";
             }
             if (prettyPrint) {
-                return objectMapperWithAutoType.writerWithDefaultPrettyPrinter().writeValueAsString(o);
+                if (ignoreAutoType) {
+                    return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(o);
+                }
+                else {
+                    return objectMapperWithAutoType.writerWithDefaultPrettyPrinter().writeValueAsString(o);
+                }
+
             }
             else {
-                return objectMapperWithAutoType.writeValueAsString(o);
+                if (ignoreAutoType) {
+                    return objectMapper.writeValueAsString(o);
+                }
+                else {
+                    return objectMapperWithAutoType.writeValueAsString(o);
+                }
             }
         } catch (JsonProcessingException e) {
             throw new RuntimeException("Parse object to json error", e);
diff --git a/test/src/test/java/io/seata/saga/engine/StateMachineAsyncTests.java b/test/src/test/java/io/seata/saga/engine/StateMachineAsyncTests.java
index ea71c40d5..29a3626f2 100644
--- a/test/src/test/java/io/seata/saga/engine/StateMachineAsyncTests.java
+++ b/test/src/test/java/io/seata/saga/engine/StateMachineAsyncTests.java
@@ -219,7 +219,7 @@ public class StateMachineAsyncTests {
     }
 
     @Test
-    public void testStateMachineWithComplextParams() {
+    public void testStateMachineWithComplexParams() {
 
         long start = System.currentTimeMillis();
 
diff --git a/test/src/test/java/io/seata/saga/engine/StateMachineTests.java b/test/src/test/java/io/seata/saga/engine/StateMachineTests.java
index 95368d345..a46ffedee 100644
--- a/test/src/test/java/io/seata/saga/engine/StateMachineTests.java
+++ b/test/src/test/java/io/seata/saga/engine/StateMachineTests.java
@@ -20,12 +20,14 @@ import io.seata.saga.engine.mock.DemoService.People;
 import io.seata.saga.statelang.domain.DomainConstants;
 import io.seata.saga.statelang.domain.ExecutionStatus;
 import io.seata.saga.statelang.domain.StateMachineInstance;
+import io.seata.saga.statelang.parser.JsonParserFactory;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -279,7 +281,36 @@ public class StateMachineTests {
     }
 
     @Test
-    public void testStateMachineWithComplextParams() {
+    public void testStateComplexParams() {
+
+        People people1 = new People();
+        people1.setName("lilei");
+        people1.setAge(18);
+
+        People people2 = new People();
+        people2.setName("lilei2");
+        people2.setAge(19);
+
+        People people3 = new People();
+        people3.setName("lilei3");
+        people3.setAge(20);
+
+        People people4 = new People();
+        people4.setName("lilei4");
+        people4.setAge(21);
+
+        people1.setChildrenArray(new People[] {people2});
+        people1.setChildrenList(Arrays.asList(people3));
+        Map<String, People> map1 = new HashMap<>(1);
+        map1.put("lilei4", people4);
+        people1.setChildrenMap(map1);
+
+        String json = JsonParserFactory.getJsonParser("jackson").toJsonString(people1, false, true);
+        System.out.println(json);
+    }
+
+    @Test
+    public void testStateMachineWithComplexParams() {
 
         long start = System.currentTimeMillis();
 
diff --git a/test/src/test/java/io/seata/saga/engine/db/StateMachineDBTests.java b/test/src/test/java/io/seata/saga/engine/db/StateMachineDBTests.java
index ba74a08b4..29994e59b 100644
--- a/test/src/test/java/io/seata/saga/engine/db/StateMachineDBTests.java
+++ b/test/src/test/java/io/seata/saga/engine/db/StateMachineDBTests.java
@@ -24,6 +24,8 @@ import io.seata.saga.engine.AsyncCallback;
 import io.seata.saga.engine.StateMachineEngine;
 import io.seata.saga.engine.exception.EngineExecutionException;
 import io.seata.saga.engine.impl.DefaultStateMachineConfig;
+import io.seata.saga.engine.mock.DemoService.Engineer;
+import io.seata.saga.engine.mock.DemoService.People;
 import io.seata.saga.proctrl.ProcessContext;
 import io.seata.saga.statelang.domain.DomainConstants;
 import io.seata.saga.statelang.domain.ExecutionStatus;
@@ -248,6 +250,36 @@ public class StateMachineDBTests extends AbstractServerTest {
         Assertions.assertTrue(GlobalStatus.CommitRetrying.equals(globalTransaction.getStatus()));
     }
 
+    @Test
+    public void testStateMachineWithComplexParams() {
+
+        long start = System.currentTimeMillis();
+
+        Map<String, Object> paramMap = new HashMap<>(1);
+        People people = new People();
+        people.setName("lilei");
+        people.setAge(18);
+
+        Engineer engineer = new Engineer();
+        engineer.setName("programmer");
+
+        paramMap.put("people", people);
+        paramMap.put("career", engineer);
+
+        String stateMachineName = "simpleStateMachineWithComplexParamsJackson";
+
+        StateMachineInstance instance = stateMachineEngine.start(stateMachineName, null, paramMap);
+
+        People peopleResult = (People) instance.getEndParams().get("complexParameterMethodResult");
+        Assertions.assertNotNull(peopleResult);
+        Assertions.assertTrue(people.getName().equals(people.getName()));
+
+        long cost = System.currentTimeMillis() - start;
+        System.out.println("====== XID: " + instance.getId() + " cost :" + cost);
+
+        Assertions.assertTrue(ExecutionStatus.SU.equals(instance.getStatus()));
+    }
+
     @Test
     public void testCompensationStateMachine() throws Exception {
 
diff --git a/test/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineAsyncDBMockServerTests.java b/test/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineAsyncDBMockServerTests.java
index 100873336..9e60fe92c 100644
--- a/test/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineAsyncDBMockServerTests.java
+++ b/test/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineAsyncDBMockServerTests.java
@@ -177,7 +177,7 @@ public class StateMachineAsyncDBMockServerTests {
     }
 
     @Test
-    public void testStateMachineWithComplextParams() {
+    public void testStateMachineWithComplexParams() {
 
         long start = System.currentTimeMillis();
 
@@ -187,7 +187,7 @@ public class StateMachineAsyncDBMockServerTests {
         people.setAge(18);
         paramMap.put("people", people);
 
-        String stateMachineName = "simpleStateMachineWithComplexParams";
+        String stateMachineName = "simpleStateMachineWithComplexParamsJackson";
 
         StateMachineInstance inst = stateMachineEngine.startAsync(stateMachineName, null, paramMap, callback);
 
diff --git a/test/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineDBMockServerTests.java b/test/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineDBMockServerTests.java
index cd6a458d4..5eab4716c 100644
--- a/test/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineDBMockServerTests.java
+++ b/test/src/test/java/io/seata/saga/engine/db/mockserver/StateMachineDBMockServerTests.java
@@ -613,7 +613,7 @@ public class StateMachineDBMockServerTests {
     }
 
     @Test
-    public void testStateMachineWithComplextParams() {
+    public void testStateMachineWithComplexParams() {
 
         long start = System.currentTimeMillis();
 
@@ -628,7 +628,7 @@ public class StateMachineDBMockServerTests {
         paramMap.put("people", people);
         paramMap.put("career", engineer);
 
-        String stateMachineName = "simpleStateMachineWithComplexParams";
+        String stateMachineName = "simpleStateMachineWithComplexParamsJackson";
 
         StateMachineInstance instance = stateMachineEngine.start(stateMachineName, null, paramMap);
 
diff --git a/test/src/test/resources/saga/statelang/simple_statelang_with_complex_params.json b/test/src/test/resources/saga/statelang/simple_statelang_with_complex_params.json
index e5c5a41dc..5c6f1cb47 100644
--- a/test/src/test/resources/saga/statelang/simple_statelang_with_complex_params.json
+++ b/test/src/test/resources/saga/statelang/simple_statelang_with_complex_params.json
@@ -1,6 +1,6 @@
 {
     "Name": "simpleStateMachineWithComplexParams",
-    "Comment": "甯﹀鏉傚弬鏁扮殑娴嬭瘯鐘舵€佹満瀹氫箟",
+    "Comment": "甯﹀鏉傚弬鏁扮殑娴嬭瘯鐘舵€佹満瀹氫箟fastjson鏍煎紡",
     "StartState": "FirstState",
     "Version": "0.0.1",
     "States": {
diff --git a/test/src/test/resources/saga/statelang/simple_statelang_with_complex_params_jackson.json b/test/src/test/resources/saga/statelang/simple_statelang_with_complex_params_jackson.json
new file mode 100644
index 000000000..1ebb00066
--- /dev/null
+++ b/test/src/test/resources/saga/statelang/simple_statelang_with_complex_params_jackson.json
@@ -0,0 +1,141 @@
+{
+    "Name": "simpleStateMachineWithComplexParamsJackson",
+    "Comment": "甯﹀鏉傚弬鏁扮殑娴嬭瘯鐘舵€佹満瀹氫箟jackson鏍煎紡",
+    "StartState": "FirstState",
+    "Version": "0.0.1",
+    "States": {
+        "FirstState": {
+            "Type": "ServiceTask",
+            "ServiceName": "demoService",
+            "ServiceMethod": "complexParameterMethod",
+            "Next": "ChoiceState",
+            "ParameterTypes" : ["java.lang.String", "int", "io.seata.saga.engine.mock.DemoService$People", "[Lio.seata.saga.engine.mock.DemoService$People;", "java.util.List", "java.util.Map"],
+            "Input": [
+                "$.[people].name",
+                "$.[people].age",
+                {
+                    "@type": "io.seata.saga.engine.mock.DemoService$People",
+                    "name": "lilei",
+                    "age": 18,
+                    "childrenArray": [
+                        "[Lio.seata.saga.engine.mock.DemoService$People;",
+                        [
+                            {
+                                "@type": "io.seata.saga.engine.mock.DemoService$People",
+                                "name": "lilei",
+                                "age": 18
+                            },
+                            {
+                                "@type": "io.seata.saga.engine.mock.DemoService$People",
+                                "name": "lilei",
+                                "age": 18
+                            }
+                        ]
+                    ],
+                    "childrenList": [
+                        "java.util.ArrayList",
+                        [
+                            {
+                                "@type": "io.seata.saga.engine.mock.DemoService$People",
+                                "name": "lilei",
+                                "age": 18
+                            },
+                            {
+                                "@type": "io.seata.saga.engine.mock.DemoService$People",
+                                "name": "lilei",
+                                "age": 18
+                            }
+                        ]
+                    ],
+                    "childrenMap": {
+                        "@type": "java.util.LinkedHashMap",
+                        "lilei": {
+                            "@type": "io.seata.saga.engine.mock.DemoService$People",
+                            "name": "lilei",
+                            "age": 18
+                        }
+                    }
+                },
+                [
+                    "[Lio.seata.saga.engine.mock.DemoService$People;",
+                    [
+                        {
+                            "@type": "io.seata.saga.engine.mock.DemoService$People",
+                            "name": "$.[people].name",
+                            "age": "$.[people].age"
+                        },
+                        {
+                            "@type": "io.seata.saga.engine.mock.DemoService$People",
+                            "name": "$.[people].name",
+                            "age": "$.[people].age"
+                        }
+                    ]
+                ],
+                [
+                    "java.util.ArrayList",
+                    [
+                        {
+                            "@type": "io.seata.saga.engine.mock.DemoService$People",
+                            "name": "$.[people].name",
+                            "age": "$.[people].age"
+                        }
+                    ]
+                ],
+                {
+                    "@type": "java.util.LinkedHashMap",
+                    "lilei": {
+                        "@type": "io.seata.saga.engine.mock.DemoService$People",
+                        "name": "$.[people].name",
+                        "age": "$.[people].age"
+                    }
+                }
+            ],
+            "Output": {
+                "complexParameterMethodResult": "$.#root"
+            }
+        },
+        "ChoiceState":{
+            "Type": "Choice",
+            "Choices":[
+                {
+                    "Expression":"[complexParameterMethodResult].age > 0",
+                    "Next":"SecondState"
+                },
+                {
+                    "Expression":"[complexParameterMethodResult].age <= 0",
+                    "Next":"ThirdState"
+                }
+            ],
+            "Default":"Fail"
+        },
+        "SecondState": {
+            "Type": "ServiceTask",
+            "ServiceName": "demoService",
+            "ServiceMethod": "interfaceParameterMethod",
+            "Input": [
+                "$.[career]"
+            ],
+            "Output": {
+                "secondStateResult": "$.#root"
+            },
+            "Next": "ThirdState"
+        },
+        "ThirdState": {
+            "Type": "ServiceTask",
+            "ServiceName": "demoService",
+            "ServiceMethod": "interfaceParameterMethod",
+            "Input": [
+                "$.[secondStateResult]"
+            ],
+            "Next": "Succeed"
+        },
+        "Succeed": {
+            "Type":"Succeed"
+        },
+        "Fail": {
+            "Type":"Fail",
+            "ErrorCode": "NOT_FOUND",
+            "Message": "not found"
+        }
+    }
+}
\ No newline at end of file
-- 
GitLab