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