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}