This is an automated email from the ASF dual-hosted git repository.

jianbin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 91eb505385 feature: add TCC three-phase hooks (#6766)
91eb505385 is described below

commit 91eb5053858c569e342d8695ef36cdf88b9f9680
Author: iAmClever <158091...@qq.com>
AuthorDate: Mon Sep 2 14:21:34 2024 +0800

    feature: add TCC three-phase hooks (#6766)
---
 changes/en-us/2.x.md                               |   1 +
 changes/zh-cn/2.x.md                               |   1 +
 .../integration/tx/api/fence/hook/TccHook.java     |  53 +++++
 .../tx/api/fence/hook/TccHookManager.java          |  73 +++++++
 .../api/interceptor/ActionInterceptorHandler.java  |  40 +++-
 .../tx/api/fence/hook/TccHookManagerTest.java      |  76 +++++++
 .../apache/seata/rm/tcc/TCCResourceManager.java    |  97 ++++++++-
 .../java/org/apache/seata/rm/tcc/TccHookTest.java  | 232 +++++++++++++++++++++
 8 files changed, 570 insertions(+), 3 deletions(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index f63bc18783..c3a77f1688 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -7,6 +7,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#6226](https://github.com/apache/incubator-seata/pull/6226)] multi-version 
seata protocol support
 - [[#6537](https://github.com/apache/incubator-seata/pull/6537)] support 
Namingserver
 - [[#6538](https://github.com/apache/incubator-seata/pull/6538)] Integration 
of naming server on the Seata server side
+- [[#6766](https://github.com/apache/incubator-seata/pull/6766)] add TCC 
three-phase hooks
 
 ### bugfix:
 - [[#6592](https://github.com/apache/incubator-seata/pull/6592)] fix @Async 
annotation not working in ClusterWatcherManager
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 177e1417f2..c1046c7c66 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -7,6 +7,7 @@
 - [[#6226](https://github.com/apache/incubator-seata/pull/6226)] 
支持seata私有协议多版本兼容
 - [[#6537](https://github.com/apache/incubator-seata/pull/6537)] 支持 
Namingserver
 - [[#6538](https://github.com/apache/incubator-seata/pull/6538)] seata 
server端集成naming server
+- [[#6766](https://github.com/apache/incubator-seata/pull/6766)] 
添加tcc三阶段钩子函数,方便用户拓展业务逻辑(比如多数据源情况下可以用于切换数据源)
 
 ### bugfix:
 - [[#6592](https://github.com/apache/incubator-seata/pull/6592)] fix 
@Async注解ClusterWatcherManager中不生效的问题
diff --git 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHook.java
 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHook.java
new file mode 100644
index 0000000000..e0e57854cc
--- /dev/null
+++ 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHook.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.seata.integration.tx.api.fence.hook;
+
+
+import org.apache.seata.rm.tcc.api.BusinessActionContext;
+
+public interface TccHook {
+
+    /**
+     * before tcc prepare
+     */
+    void beforeTccPrepare(String xid, Long branchId, String actionName, 
BusinessActionContext context);
+
+    /**
+     * after tcc prepare
+     */
+    void afterTccPrepare(String xid, Long branchId, String actionName, 
BusinessActionContext context);
+
+    /**
+     * before tcc commit
+     */
+    void beforeTccCommit(String xid, Long branchId, String actionName, 
BusinessActionContext context);
+
+    /**
+     * after tcc commit
+     */
+    void afterTccCommit(String xid, Long branchId, String actionName, 
BusinessActionContext context);
+
+    /**
+     * before tcc rollback
+     */
+    void beforeTccRollback(String xid, Long branchId, String actionName, 
BusinessActionContext context);
+
+    /**
+     * after tcc rollback
+     */
+    void afterTccRollback(String xid, Long branchId, String actionName, 
BusinessActionContext context);
+}
diff --git 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManager.java
 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManager.java
new file mode 100644
index 0000000000..e6d537c73f
--- /dev/null
+++ 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManager.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.seata.integration.tx.api.fence.hook;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class TccHookManager {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(TccHookManager.class);
+
+    private TccHookManager() {
+
+    }
+
+    private static final List<TccHook> TCC_HOOKS = new 
CopyOnWriteArrayList<>();
+    // Cache unmodifiable lists
+    private volatile static List<TccHook> CACHED_UNMODIFIABLE_HOOKS = null;
+
+    /**
+     * get the hooks
+     * @return tccHook list
+     */
+    public static List<TccHook> getHooks() {
+        if (CACHED_UNMODIFIABLE_HOOKS == null) {
+            synchronized (TccHookManager.class) {
+                if (CACHED_UNMODIFIABLE_HOOKS == null) {
+                    CACHED_UNMODIFIABLE_HOOKS = 
Collections.unmodifiableList(TCC_HOOKS);
+                }
+            }
+        }
+        return CACHED_UNMODIFIABLE_HOOKS;
+    }
+
+    /**
+     * add new hook
+     * @param tccHook tccHook
+     */
+    public static void registerHook(TccHook tccHook) {
+        if (tccHook == null) {
+            throw new NullPointerException("tccHook must not be null");
+        }
+        TCC_HOOKS.add(tccHook);
+        CACHED_UNMODIFIABLE_HOOKS = null;
+        LOGGER.info("TccHook registered succeeded! TccHooks size: {}", 
TCC_HOOKS.size());
+    }
+
+    /**
+     * clear hooks
+     */
+    public static void clear() {
+        TCC_HOOKS.clear();
+        CACHED_UNMODIFIABLE_HOOKS = null;
+        LOGGER.info("All TccHooks have been cleared.");
+    }
+}
\ No newline at end of file
diff --git 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/ActionInterceptorHandler.java
 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/ActionInterceptorHandler.java
index b3600cdc55..3706ca50e9 100644
--- 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/ActionInterceptorHandler.java
+++ 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/ActionInterceptorHandler.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
 import java.lang.reflect.UndeclaredThrowableException;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.seata.common.Constants;
@@ -32,6 +33,8 @@ import org.apache.seata.common.util.CollectionUtils;
 import org.apache.seata.common.util.NetUtil;
 import org.apache.seata.core.context.RootContext;
 import org.apache.seata.integration.tx.api.fence.DefaultCommonFenceHandler;
+import org.apache.seata.integration.tx.api.fence.hook.TccHook;
+import org.apache.seata.integration.tx.api.fence.hook.TccHookManager;
 import org.apache.seata.integration.tx.api.util.JsonUtil;
 import org.apache.seata.rm.DefaultResourceManager;
 import org.apache.seata.rm.tcc.api.BusinessActionContext;
@@ -87,7 +90,7 @@ public class ActionInterceptorHandler {
         try {
             //share actionContext implicitly
             BusinessActionContextUtil.setContext(actionContext);
-
+            doBeforeTccPrepare(xid, branchId, actionName, actionContext);
             if (businessActionParam.getUseCommonFence()) {
                 try {
                     // Use common Fence, and return the business result
@@ -105,6 +108,7 @@ public class ActionInterceptorHandler {
             }
         } finally {
             try {
+                doAfterTccPrepare(xid, branchId, actionName, actionContext);
                 //to report business action context finally if the 
actionContext.getUpdated() is true
                 BusinessActionContextUtil.reportContext(actionContext);
             } finally {
@@ -119,6 +123,40 @@ public class ActionInterceptorHandler {
         }
     }
 
+    /**
+     * to do some business operations before tcc prepare
+     * @param xid          the xid
+     * @param branchId     the branchId
+     * @param actionName   the actionName
+     * @param context      the business action context
+     */
+    private void doBeforeTccPrepare(String xid, String branchId, String 
actionName, BusinessActionContext context) {
+        List<TccHook> hooks = TccHookManager.getHooks();
+        if (hooks.isEmpty()) {
+            return;
+        }
+        for (TccHook hook : hooks) {
+            hook.beforeTccPrepare(xid, Long.valueOf(branchId), actionName, 
context);
+        }
+    }
+
+    /**
+     * to do some business operations after tcc prepare
+     * @param xid          the xid
+     * @param branchId     the branchId
+     * @param actionName   the actionName
+     * @param context      the business action context
+     */
+    private void doAfterTccPrepare(String xid, String branchId, String 
actionName, BusinessActionContext context) {
+        List<TccHook> hooks = TccHookManager.getHooks();
+        if (hooks.isEmpty()) {
+            return;
+        }
+        for (TccHook hook : hooks) {
+            hook.afterTccPrepare(xid, Long.valueOf(branchId), actionName, 
context);
+        }
+    }
+
     /**
      * Get or create action context, and reset to arguments
      *
diff --git 
a/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManagerTest.java
 
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManagerTest.java
new file mode 100644
index 0000000000..ebf024a1dc
--- /dev/null
+++ 
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/hook/TccHookManagerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.seata.integration.tx.api.fence.hook;
+
+
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+
+
+public class TccHookManagerTest {
+
+    @BeforeEach
+    public void setUp() {
+        TccHookManager.clear();
+    }
+
+    @Test
+    public void testRegisterHook() {
+        TccHook hook = mock(TccHook.class);
+        TccHookManager.registerHook(hook);
+
+        List<TccHook> hooks = TccHookManager.getHooks();
+        assertEquals(1, hooks.size());
+        assertTrue(hooks.contains(hook));
+    }
+
+    @Test
+    public void testClear() {
+        TccHook hook = mock(TccHook.class);
+        TccHookManager.registerHook(hook);
+        List<TccHook> hooks = TccHookManager.getHooks();
+        assertEquals(1, hooks.size());
+        assertTrue(hooks.contains(hook));
+
+        TccHookManager.clear();
+
+        assertTrue(TccHookManager.getHooks().isEmpty());
+    }
+
+    @Test
+    public void testGetHooks() {
+        TccHook hook1 = mock(TccHook.class);
+        TccHook hook2 = mock(TccHook.class);
+        TccHookManager.registerHook(hook1);
+        TccHookManager.registerHook(hook2);
+
+        List<TccHook> hooks = TccHookManager.getHooks();
+        assertEquals(2, hooks.size());
+        assertTrue(hooks.contains(hook1));
+        assertTrue(hooks.contains(hook2));
+
+        // Check unmodifiable list
+        assertThrows(UnsupportedOperationException.class, () -> 
hooks.add(mock(TccHook.class)));
+    }
+}
diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java 
b/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java
index 2ad3c2b373..ca49136ae1 100644
--- a/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java
+++ b/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java
@@ -18,6 +18,7 @@ package org.apache.seata.rm.tcc;
 
 import java.lang.reflect.Method;
 import java.lang.reflect.UndeclaredThrowableException;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -31,6 +32,8 @@ import org.apache.seata.core.model.BranchStatus;
 import org.apache.seata.core.model.BranchType;
 import org.apache.seata.core.model.Resource;
 import org.apache.seata.integration.tx.api.fence.DefaultCommonFenceHandler;
+import org.apache.seata.integration.tx.api.fence.hook.TccHook;
+import org.apache.seata.integration.tx.api.fence.hook.TccHookManager;
 import org.apache.seata.integration.tx.api.remoting.TwoPhaseResult;
 import org.apache.seata.rm.AbstractResourceManager;
 import org.apache.seata.rm.tcc.api.BusinessActionContext;
@@ -113,14 +116,16 @@ public class TCCResourceManager extends 
AbstractResourceManager {
         if (targetTCCBean == null || commitMethod == null) {
             throw new ShouldNeverHappenException(String.format("TCC resource 
is not available, resourceId: %s", resourceId));
         }
+        BusinessActionContext businessActionContext = null;
         try {
             //BusinessActionContext
-            BusinessActionContext businessActionContext = 
BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId,
+            businessActionContext = 
BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId,
                     applicationData);
 
             Object[] args = this.getTwoPhaseCommitArgs(tccResource, 
businessActionContext);
             //share actionContext implicitly
             BusinessActionContextUtil.setContext(businessActionContext);
+            doBeforeTccCommit(xid, branchId, tccResource.getActionName(), 
businessActionContext);
             Object ret;
             boolean result;
             // add idempotent and anti hanging
@@ -149,6 +154,7 @@ public class TCCResourceManager extends 
AbstractResourceManager {
             LOGGER.error(msg, ExceptionUtil.unwrap(t));
             return BranchStatus.PhaseTwo_CommitFailed_Retryable;
         } finally {
+            doAfterTccCommit(xid, branchId, tccResource.getActionName(), 
businessActionContext);
             // clear the action context
             BusinessActionContextUtil.clear();
         }
@@ -177,13 +183,15 @@ public class TCCResourceManager extends 
AbstractResourceManager {
         if (targetTCCBean == null || rollbackMethod == null) {
             throw new ShouldNeverHappenException(String.format("TCC resource 
is not available, resourceId: %s", resourceId));
         }
+        BusinessActionContext businessActionContext = null;
         try {
             //BusinessActionContext
-            BusinessActionContext businessActionContext = 
BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId,
+            businessActionContext = 
BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId,
                     applicationData);
             Object[] args = this.getTwoPhaseRollbackArgs(tccResource, 
businessActionContext);
             //share actionContext implicitly
             BusinessActionContextUtil.setContext(businessActionContext);
+            doBeforeTccRollback(xid, branchId, tccResource.getActionName(), 
businessActionContext);
             Object ret;
             boolean result;
             // add idempotent and anti hanging
@@ -213,11 +221,96 @@ public class TCCResourceManager extends 
AbstractResourceManager {
             LOGGER.error(msg, ExceptionUtil.unwrap(t));
             return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
         } finally {
+            doAfterTccRollback(xid, branchId, tccResource.getActionName(), 
businessActionContext);
             // clear the action context
             BusinessActionContextUtil.clear();
         }
     }
 
+    /**
+     * to do some business operations before tcc rollback
+     * @param xid          the xid
+     * @param branchId     the branchId
+     * @param actionName   the actionName
+     * @param context      the business action context
+     */
+    private void doBeforeTccRollback(String xid, long branchId, String 
actionName, BusinessActionContext context) {
+        List<TccHook> hooks = TccHookManager.getHooks();
+        if (hooks.isEmpty()) {
+            return;
+        }
+        for (TccHook hook : hooks) {
+            try {
+                hook.beforeTccRollback(xid, branchId, actionName, context);
+            } catch (Exception e) {
+                LOGGER.error("Failed execute beforeTccRollback in hook {}", 
e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * to do some business operations after tcc rollback
+     * @param xid          the xid
+     * @param branchId     the branchId
+     * @param actionName   the actionName
+     * @param context      the business action context
+     */
+    private void doAfterTccRollback(String xid, long branchId, String 
actionName, BusinessActionContext context) {
+        List<TccHook> hooks = TccHookManager.getHooks();
+        if (hooks.isEmpty()) {
+            return;
+        }
+        for (TccHook hook : hooks) {
+            try {
+                hook.afterTccRollback(xid, branchId, actionName, context);
+            } catch (Exception e) {
+                LOGGER.error("Failed execute afterTccRollback in hook {}", 
e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * to do some business operations before tcc commit
+     * @param xid          the xid
+     * @param branchId     the branchId
+     * @param actionName   the actionName
+     * @param context      the business action context
+     */
+    private void doBeforeTccCommit(String xid, long branchId, String 
actionName, BusinessActionContext context) {
+        List<TccHook> hooks = TccHookManager.getHooks();
+        if (hooks.isEmpty()) {
+            return;
+        }
+        for (TccHook hook : hooks) {
+            try {
+                hook.beforeTccCommit(xid, branchId, actionName, context);
+            } catch (Exception e) {
+                LOGGER.error("Failed execute beforeTccCommit in hook {}", 
e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * to do some business operations after tcc commit
+     * @param xid          the xid
+     * @param branchId     the branchId
+     * @param actionName   the actionName
+     * @param context      the business action context
+     */
+    private void doAfterTccCommit(String xid, long branchId, String 
actionName, BusinessActionContext context) {
+        List<TccHook> hooks = TccHookManager.getHooks();
+        if (hooks.isEmpty()) {
+            return;
+        }
+        for (TccHook hook : hooks) {
+            try {
+                hook.afterTccCommit(xid, branchId, actionName, context);
+            } catch (Exception e) {
+                LOGGER.error("Failed execute afterTccCommit in hook {}", 
e.getMessage(), e);
+            }
+        }
+    }
+
     /**
      * get phase two commit method's args
      * @param tccResource tccResource
diff --git a/tcc/src/test/java/org/apache/seata/rm/tcc/TccHookTest.java 
b/tcc/src/test/java/org/apache/seata/rm/tcc/TccHookTest.java
new file mode 100644
index 0000000000..6663e0713c
--- /dev/null
+++ b/tcc/src/test/java/org/apache/seata/rm/tcc/TccHookTest.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.seata.rm.tcc;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.seata.common.executor.Callback;
+import org.apache.seata.core.exception.TransactionException;
+import org.apache.seata.core.model.BranchType;
+import org.apache.seata.integration.tx.api.fence.hook.TccHook;
+import org.apache.seata.integration.tx.api.fence.hook.TccHookManager;
+import 
org.apache.seata.integration.tx.api.interceptor.ActionInterceptorHandler;
+import 
org.apache.seata.integration.tx.api.interceptor.TwoPhaseBusinessActionParam;
+import org.apache.seata.rm.tcc.api.BusinessActionContext;
+import org.apache.seata.core.model.Resource;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class TccHookTest {
+    private MyTccHook tccHook;
+    private String xid;
+    private Long branchId;
+    private String actionName;
+    private BusinessActionContext context;
+    private TestActionInterceptorHandler actionInterceptorHandler;
+    private TCCResourceManager tccResourceManager;
+
+    @BeforeEach
+    public void setUp() throws NoSuchFieldException, IllegalAccessException, 
NoSuchMethodException {
+        tccHook = Mockito.spy(new MyTccHook());
+        xid = "test-xid";
+        branchId = 12345L;
+        actionName = "testAction";
+        context = new BusinessActionContext();
+        TccHookManager.clear();
+        TccHookManager.registerHook(tccHook);
+
+        actionInterceptorHandler = Mockito.spy(new 
TestActionInterceptorHandler());
+        TCCResourceManager tccResourceManagerObject = new TCCResourceManager();
+        TCCResource tccResource = mock(TCCResource.class);
+        Mockito.doReturn(actionName)
+                .when(tccResource).getResourceId();
+        Mockito.doReturn(actionName)
+                .when(tccResource).getActionName();
+        TestTccThreePhaseHandler testTccThreePhaseHandler = new 
TestTccThreePhaseHandler();
+        Mockito.doReturn(testTccThreePhaseHandler)
+                .when(tccResource).getTargetBean();
+
+        Mockito.doReturn(new String[0])
+                .when(tccResource).getPhaseTwoCommitKeys();
+        Mockito.doReturn(new Class[0])
+                .when(tccResource).getCommitArgsClasses();
+
+        Mockito.doReturn(new String[0])
+                .when(tccResource).getPhaseTwoRollbackKeys();
+        Mockito.doReturn(new Class[0])
+                .when(tccResource).getRollbackArgsClasses();
+
+        Method commitMethod = 
testTccThreePhaseHandler.getClass().getMethod("commit");
+        Mockito.doReturn(commitMethod)
+                .when(tccResource).getCommitMethod();
+
+        Method rollbackMethod = 
testTccThreePhaseHandler.getClass().getMethod("rollback");
+        Mockito.doReturn(rollbackMethod)
+                .when(tccResource).getRollbackMethod();
+
+        Map<String, Resource> tccResourceCache = new ConcurrentHashMap<>();
+        tccResourceCache.put(actionName, tccResource);
+        setPrivateField(tccResourceManagerObject, "tccResourceCache", 
tccResourceCache);
+        tccResourceManager = Mockito.spy(tccResourceManagerObject);
+    }
+
+    @Test
+    public void testBeforeTccPrepare() {
+        for (TccHook hook : TccHookManager.getHooks()) {
+            hook.beforeTccPrepare(xid, branchId, actionName, context);
+        }
+        verify(tccHook).beforeTccPrepare(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void testAfterTccPrepare() {
+        for (TccHook hook : TccHookManager.getHooks()) {
+            hook.afterTccPrepare(xid, branchId, actionName, context);
+        }
+        verify(tccHook).afterTccPrepare(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void testBeforeTccCommit() {
+        for (TccHook hook : TccHookManager.getHooks()) {
+            hook.beforeTccCommit(xid, branchId, actionName, context);
+        }
+        verify(tccHook).beforeTccCommit(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void testAfterTccCommit() {
+        for (TccHook hook : TccHookManager.getHooks()) {
+            hook.afterTccCommit(xid, branchId, actionName, context);
+        }
+        verify(tccHook).afterTccCommit(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void testBeforeTccRollback() {
+        for (TccHook hook : TccHookManager.getHooks()) {
+            hook.beforeTccRollback(xid, branchId, actionName, context);
+        }
+        verify(tccHook).beforeTccRollback(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void testAfterTccRollback() {
+        for (TccHook hook : TccHookManager.getHooks()) {
+            hook.afterTccRollback(xid, branchId, actionName, context);
+        }
+        verify(tccHook).afterTccRollback(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void testTccPrepareHook() throws Throwable {
+        TestTccThreePhaseHandler testTccThreePhaseHandler = new 
TestTccThreePhaseHandler();
+        Method method = 
testTccThreePhaseHandler.getClass().getMethod("prepare");
+        TwoPhaseBusinessActionParam twoPhaseBusinessActionParam = 
Mockito.mock(TwoPhaseBusinessActionParam.class);
+        Callback<Object> callback = Mockito.mock(Callback.class, 
Mockito.withSettings().defaultAnswer(Mockito.RETURNS_DEFAULTS));
+        Mockito.doReturn(actionName)
+                .when(twoPhaseBusinessActionParam).getActionName();
+        actionInterceptorHandler.proceed(method, null, xid, 
twoPhaseBusinessActionParam, callback);
+        verify(tccHook).beforeTccPrepare(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+        verify(tccHook).afterTccPrepare(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void testTccCommitHook() throws TransactionException {
+        tccResourceManager.branchCommit(BranchType.TCC, xid, branchId, 
actionName, null);
+        verify(tccHook).beforeTccCommit(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+        verify(tccHook).afterTccCommit(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    @Test
+    public void testTccRollbackHook() throws TransactionException {
+        tccResourceManager.branchRollback(BranchType.TCC, xid, branchId, 
actionName, null);
+        verify(tccHook).beforeTccRollback(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+        verify(tccHook).afterTccRollback(Mockito.any(), Mockito.any(), 
Mockito.any(), Mockito.any());
+    }
+
+    private void setPrivateField(Object target, String fieldName, Object 
value) throws NoSuchFieldException, IllegalAccessException {
+        Field field = target.getClass().getDeclaredField(fieldName);
+        field.setAccessible(true);
+        field.set(target, value);
+    }
+
+    public class TestActionInterceptorHandler extends ActionInterceptorHandler 
{
+        @Override
+        public BusinessActionContext 
getOrCreateActionContextAndResetToArguments(Class<?>[] parameterTypes, Object[] 
arguments) {
+            return context;
+        }
+
+        @Override
+        public String doTxActionLogStore(Method method, Object[] arguments, 
TwoPhaseBusinessActionParam businessActionParam,
+                                         BusinessActionContext actionContext) {
+            return String.valueOf(branchId);
+        }
+    }
+
+    public class TestTccThreePhaseHandler {
+        public void prepare() {
+        }
+        public void commit() {
+        }
+        public void rollback() {
+        }
+    }
+
+    public class MyTccHook implements TccHook {
+        private final Logger LOGGER = LoggerFactory.getLogger(MyTccHook.class);
+        @Override
+        public void beforeTccPrepare(String xid, Long branchId, String 
actionName, BusinessActionContext context) {
+            LOGGER.info("do some business operations before tcc prepare");
+        }
+
+        @Override
+        public void afterTccPrepare(String xid, Long branchId, String 
actionName, BusinessActionContext context) {
+            LOGGER.info("do some business operations after tcc prepare");
+        }
+
+        @Override
+        public void beforeTccCommit(String xid, Long branchId, String 
actionName, BusinessActionContext context) {
+            LOGGER.info("do some business operations before tcc commit");
+        }
+
+        @Override
+        public void afterTccCommit(String xid, Long branchId, String 
actionName, BusinessActionContext context) {
+            LOGGER.info("do some business operations after tcc commit");
+        }
+
+        @Override
+        public void beforeTccRollback(String xid, Long branchId, String 
actionName, BusinessActionContext context) {
+            LOGGER.info("do some business operations before tcc rollback");
+        }
+
+        @Override
+        public void afterTccRollback(String xid, Long branchId, String 
actionName, BusinessActionContext context) {
+            LOGGER.info("do some business operations after tcc rollback");
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@seata.apache.org
For additional commands, e-mail: notifications-h...@seata.apache.org

Reply via email to