001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.support.seata.tcc; 017 018import com.alibaba.fastjson.JSON; 019import com.jfinal.aop.Invocation; 020import com.jfinal.log.Log; 021import io.seata.common.Constants; 022import io.seata.common.exception.FrameworkException; 023import io.seata.common.util.NetUtil; 024import io.seata.common.util.ReflectionUtil; 025import io.seata.core.model.BranchType; 026import io.seata.rm.DefaultResourceManager; 027import io.seata.rm.tcc.TCCResource; 028import io.seata.rm.tcc.api.BusinessActionContext; 029import io.seata.rm.tcc.api.BusinessActionContextParameter; 030import io.seata.rm.tcc.api.BusinessActionContextUtil; 031import io.seata.rm.tcc.api.TwoPhaseBusinessAction; 032import io.seata.rm.tcc.interceptor.ActionContextUtil; 033 034import java.lang.annotation.Annotation; 035import java.lang.reflect.Method; 036import java.lang.reflect.Parameter; 037import java.util.HashMap; 038import java.util.Map; 039 040/** 041 * Handler the TCC Participant Aspect : Setting Context, Creating Branch Record 042 * 参考:https://github.com/seata/seata/blob/master/tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionInterceptorHandler.java 043 * 044 * @author zhangsen/菜农 commit: https://gitee.com/fuhai/jboot/commit/55564bfd9e6eebfc39263291d89592cd16f77498 045 */ 046public class ActionInterceptorHandler { 047 private static final Log LOGGER = Log.getLog(TccActionInterceptor.class); 048 049 /** 050 * Handler the TCC Aspect 051 * 052 * @param method the method 053 * @param arguments the arguments 054 * @param businessAction the business action 055 * @return map map 056 */ 057 public void proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction, 058 Invocation invocation) { 059 060 //TCC name 061 String actionName = businessAction.name(); 062 BusinessActionContext actionContext = new BusinessActionContext(); 063 actionContext.setXid(xid); 064 //set action name 065 actionContext.setActionName(actionName); 066 Class<?>[] types = method.getParameterTypes(); 067 Parameter[] parameters = invocation.getMethod().getParameters(); 068 int argIndex = 0; 069 for (Class<?> cls : types) { 070 if (cls.getName().equals(BusinessActionContext.class.getName())) { 071 arguments[argIndex] = actionContext; 072 break; 073 } 074 argIndex++; 075 } 076 //Creating Branch Record 077 String branchId = doTccActionLogStore(method,parameters, arguments, businessAction, actionContext); 078 try { 079 registryResource(method, invocation.getTarget(),types, businessAction); 080 } catch (NoSuchMethodException e) { 081 e.printStackTrace(); 082 } 083 actionContext.setBranchId(branchId); 084 // save the previous action context 085 BusinessActionContext previousActionContext = BusinessActionContextUtil.getContext(); 086 try { 087 //share actionContext implicitly 088 BusinessActionContextUtil.setContext(actionContext); 089 if (businessAction.useTCCFence()) { 090 // Use TCC Fence, and return the business result 091 TCCFenceHandler.prepareFence(xid, Long.valueOf(branchId), actionName); 092 } 093 invocation.invoke(); 094 } finally { 095 try { 096 //to report business action context finally if the actionContext.getUpdated() is true 097 BusinessActionContextUtil.reportContext(actionContext); 098 } finally { 099 if (previousActionContext != null) { 100 // recovery the previous action context 101 BusinessActionContextUtil.setContext(previousActionContext); 102 } else { 103 // clear the action context 104 BusinessActionContextUtil.clear(); 105 } 106 } 107 } 108 } 109 110 /** 111 * Creating Branch Record 112 * 113 * @param method the method 114 * @param arguments the arguments 115 * @param businessAction the business action 116 * @param actionContext the action context 117 * @return the string 118 */ 119 protected String doTccActionLogStore(Method method, Parameter[] parameters , Object[] arguments, TwoPhaseBusinessAction businessAction, 120 BusinessActionContext actionContext) { 121 String actionName = actionContext.getActionName(); 122 String xid = actionContext.getXid(); 123 // 124 Map<String, Object> context = fetchActionRequestContext(method, arguments,parameters); 125 context.put(Constants.ACTION_START_TIME, System.currentTimeMillis()); 126 127 //init business context 128 initBusinessContext(context, method, businessAction); 129 //Init running environment context 130 initFrameworkContext(context); 131 actionContext.setActionContext(context); 132 133 //init applicationData 134 Map<String, Object> applicationContext = new HashMap<>(4); 135 applicationContext.put(Constants.TCC_ACTION_CONTEXT, context); 136 String applicationContextStr = JSON.toJSONString(applicationContext); 137 try { 138 //registry branch record 139 Long branchId = DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid, 140 applicationContextStr, null); 141 return String.valueOf(branchId); 142 } catch (Throwable t) { 143 String msg = String.format("TCC branch Register error, xid: %s", xid); 144 LOGGER.error(msg, t); 145 throw new FrameworkException(t, msg); 146 } 147 } 148 149 /** 150 * Init running environment context 151 * 152 * @param context the context 153 */ 154 protected void initFrameworkContext(Map<String, Object> context) { 155 try { 156 context.put(Constants.HOST_NAME, NetUtil.getLocalIp()); 157 } catch (Throwable t) { 158 LOGGER.warn("getLocalIP error", t); 159 } 160 } 161 162 /** 163 * Init business context 164 * 165 * @param context the context 166 * @param method the method 167 * @param businessAction the business action 168 */ 169 protected void initBusinessContext(Map<String, Object> context, Method method, 170 TwoPhaseBusinessAction businessAction) { 171 if (method != null) { 172 //the phase one method name 173 context.put(Constants.PREPARE_METHOD, method.getName()); 174 } 175 if (businessAction != null) { 176 //the phase two method name 177 context.put(Constants.COMMIT_METHOD, businessAction.commitMethod()); 178 context.put(Constants.ROLLBACK_METHOD, businessAction.rollbackMethod()); 179 context.put(Constants.ACTION_NAME, businessAction.name()); 180 context.put(Constants.USE_TCC_FENCE, businessAction.useTCCFence()); 181 } 182 } 183 184 /** 185 * Extracting context data from parameters, add them to the context 186 * 187 * @param method the method 188 * @param arguments the arguments 189 * @return map map 190 */ 191 protected Map<String, Object> fetchActionRequestContext(Method method, Object[] arguments,Parameter[] parameters) { 192 Map<String, Object> context = new HashMap<>(8); 193 int x = 0; 194 for (Parameter p : parameters) { 195 if (!p.isNamePresent()) { 196 // 必须通过添加 -parameters 进行编译,才可以获取 Parameter 的编译前的名字 197 throw new RuntimeException(" Maven or IDE config is error. see https://jfinal.com/doc/3-3 "); 198 } 199 if (!"io.seata.rm.tcc.api.BusinessActionContext".equals(p.getType().getName())) { 200 context.put(p.getName(), arguments[x]); 201 } 202 x++; 203 } 204 return context; 205 } 206 207 public void registryResource(Method m, Object interfaceClass, Class[] arguments, TwoPhaseBusinessAction businessAction) throws NoSuchMethodException { 208 if (businessAction != null) { 209 TCCResource tccResource = new TCCResource(); 210 tccResource.setActionName(businessAction.name()); 211 tccResource.setTargetBean(interfaceClass); 212 tccResource.setPrepareMethod(m); 213 tccResource.setCommitMethodName(businessAction.commitMethod()); 214 tccResource.setCommitMethod(ReflectionUtil 215 .getMethod(interfaceClass.getClass(), businessAction.commitMethod(), 216 new Class[] {BusinessActionContext.class})); 217 tccResource.setRollbackMethodName(businessAction.rollbackMethod()); 218 tccResource.setRollbackMethod(ReflectionUtil 219 .getMethod(interfaceClass.getClass(), businessAction.rollbackMethod(), 220 new Class[] {BusinessActionContext.class})); 221 // set argsClasses 222 tccResource.setCommitArgsClasses(businessAction.commitArgsClasses()); 223 tccResource.setRollbackArgsClasses(businessAction.rollbackArgsClasses()); 224 // set phase two method's keys 225 tccResource.setPhaseTwoCommitKeys(this.getTwoPhaseArgs(tccResource.getCommitMethod(), 226 businessAction.commitArgsClasses())); 227 tccResource.setPhaseTwoRollbackKeys(this.getTwoPhaseArgs(tccResource.getRollbackMethod(), 228 businessAction.rollbackArgsClasses())); 229 //registry tcc resource 230 DefaultResourceManager.get().registerResource(tccResource); 231 } 232 } 233 234 protected String[] getTwoPhaseArgs(Method method, Class<?>[] argsClasses) { 235 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 236 String[] keys = new String[parameterAnnotations.length]; 237 /* 238 * get parameter's key 239 * if method's parameter list is like 240 * (BusinessActionContext, @BusinessActionContextParameter("a") A a, @BusinessActionContextParameter("b") B b) 241 * the keys will be [null, a, b] 242 */ 243 for (int i = 0; i < parameterAnnotations.length; i++) { 244 for (int j = 0; j < parameterAnnotations[i].length; j++) { 245 if (parameterAnnotations[i][j] instanceof BusinessActionContextParameter) { 246 BusinessActionContextParameter param = (BusinessActionContextParameter)parameterAnnotations[i][j]; 247 String key = ActionContextUtil.getParamNameFromAnnotation(param); 248 keys[i] = key; 249 break; 250 } 251 } 252 if (keys[i] == null && !(argsClasses[i].equals(BusinessActionContext.class))) { 253 throw new IllegalArgumentException("non-BusinessActionContext parameter should use annotation " + 254 "BusinessActionContextParameter"); 255 } 256 } 257 return keys; 258 } 259}