001/*
002 * Copyright 1999-2018 Alibaba Group Holding Ltd.
003 *
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 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
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.sentinel;
017
018import com.alibaba.csp.sentinel.Tracer;
019import com.alibaba.csp.sentinel.annotation.SentinelResource;
020import com.alibaba.csp.sentinel.log.RecordLog;
021import com.alibaba.csp.sentinel.slots.block.BlockException;
022import com.alibaba.csp.sentinel.util.MethodUtil;
023import com.alibaba.csp.sentinel.util.StringUtil;
024import com.jfinal.aop.Invocation;
025
026import java.lang.reflect.InvocationTargetException;
027import java.lang.reflect.Method;
028import java.lang.reflect.Modifier;
029import java.util.Arrays;
030
031/**
032 * Some common functions for Sentinel annotation aspect.
033 *
034 * @author Eric Zhao
035 */
036public abstract class AbstractSentinelInterceptor {
037
038    protected void traceException(Throwable ex) {
039        Tracer.trace(ex);
040    }
041
042    protected void traceException(Throwable ex, SentinelResource annotation) {
043        Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
044        // The ignore list will be checked first.
045        if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
046            return;
047        }
048        if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
049            traceException(ex);
050        }
051    }
052
053    /**
054     * Check whether the exception is in provided list of exception classes.
055     *
056     * @param ex         provided throwable
057     * @param exceptions list of exceptions
058     * @return true if it is in the list, otherwise false
059     */
060    protected boolean exceptionBelongsTo(Throwable ex, Class<? extends Throwable>[] exceptions) {
061        if (exceptions == null) {
062            return false;
063        }
064        for (Class<? extends Throwable> exceptionClass : exceptions) {
065            if (exceptionClass.isAssignableFrom(ex.getClass())) {
066                return true;
067            }
068        }
069        return false;
070    }
071
072    protected String getResourceName(String resourceName, /*@NonNull*/ Method method) {
073        // If resource name is present in annotation, use this value.
074        if (StringUtil.isNotBlank(resourceName)) {
075            return resourceName;
076        }
077        // Parse name of target method.
078        return MethodUtil.resolveMethodName(method);
079    }
080
081    protected Object handleFallback(Invocation inv, SentinelResource annotation, Throwable ex)
082            throws Throwable {
083        return handleFallback(inv, annotation.fallback(), annotation.defaultFallback(), annotation.fallbackClass(), ex);
084    }
085
086    protected Object handleFallback(Invocation inv, String fallback, String defaultFallback,
087                                    Class<?>[] fallbackClass, Throwable ex) throws Throwable {
088        Object[] originArgs = inv.getArgs();
089
090        // Execute fallback function if configured.
091        Method fallbackMethod = extractFallbackMethod(inv, fallback, fallbackClass);
092        if (fallbackMethod != null) {
093            // Construct args.
094            int paramCount = fallbackMethod.getParameterTypes().length;
095            Object[] args;
096            if (paramCount == originArgs.length) {
097                args = originArgs;
098            } else {
099                args = Arrays.copyOf(originArgs, originArgs.length + 1);
100                args[args.length - 1] = ex;
101            }
102
103            try {
104                if (isStatic(fallbackMethod)) {
105                    return fallbackMethod.invoke(null, args);
106                }
107                return fallbackMethod.invoke(inv.getTarget(), args);
108            } catch (InvocationTargetException e) {
109                // throw the actual exception
110                throw e.getTargetException();
111            }
112        }
113        // If fallback is absent, we'll try the defaultFallback if provided.
114        return handleDefaultFallback(inv, defaultFallback, fallbackClass, ex);
115    }
116
117    protected Object handleDefaultFallback(Invocation inv, String defaultFallback,  Class<?>[] fallbackClass, Throwable ex) throws Throwable {
118        // Execute the default fallback function if configured.
119        Method fallbackMethod = extractDefaultFallbackMethod(inv, defaultFallback, fallbackClass);
120        if (fallbackMethod != null) {
121            // Construct args.
122            Object[] args = fallbackMethod.getParameterTypes().length == 0 ? new Object[0] : new Object[]{ex};
123            try {
124                if (isStatic(fallbackMethod)) {
125                    return fallbackMethod.invoke(null, args);
126                }
127                return fallbackMethod.invoke(inv.getTarget(), args);
128            } catch (InvocationTargetException e) {
129                // throw the actual exception
130                throw e.getTargetException();
131            }
132        }
133
134        // If no any fallback is present, then directly throw the exception.
135        throw ex;
136    }
137
138    protected Object handleBlockException(Invocation inv, SentinelResource annotation, BlockException ex)
139            throws Throwable {
140
141        // Execute block handler if configured.
142        Method blockHandlerMethod = extractBlockHandlerMethod(inv, annotation.blockHandler(),
143                annotation.blockHandlerClass());
144        if (blockHandlerMethod != null) {
145            Object[] originArgs = inv.getArgs();
146            // Construct args.
147            Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1);
148            args[args.length - 1] = ex;
149            try {
150                if (isStatic(blockHandlerMethod)) {
151                    return blockHandlerMethod.invoke(null, args);
152                }
153                return blockHandlerMethod.invoke(inv.getTarget(), args);
154            } catch (InvocationTargetException e) {
155                // throw the actual exception
156                throw e.getTargetException();
157            }
158        }
159
160        // If no block handler is present, then go to fallback.
161        return handleFallback(inv, annotation, ex);
162    }
163
164    private Method extractFallbackMethod(Invocation inv, String fallbackName, Class<?>[] locationClass) {
165        if (StringUtil.isBlank(fallbackName)) {
166            return null;
167        }
168        boolean mustStatic = locationClass != null && locationClass.length >= 1;
169        Class<?> clazz = mustStatic ? locationClass[0] : inv.getTarget().getClass();
170        MethodWrapper m = ResourceMetadataRegistry.lookupFallback(clazz, fallbackName);
171        if (m == null) {
172            // First time, resolve the fallback.
173            Method method = resolveFallbackInternal(inv, fallbackName, clazz, mustStatic);
174            // Cache the method instance.
175            ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, method);
176            return method;
177        }
178        if (!m.isPresent()) {
179            return null;
180        }
181        return m.getMethod();
182    }
183
184    private Method extractDefaultFallbackMethod(Invocation inv, String defaultFallback,
185                                                Class<?>[] locationClass) {
186        if (StringUtil.isBlank(defaultFallback)) {
187            return null;
188        }
189        boolean mustStatic = locationClass != null && locationClass.length >= 1;
190        Class<?> clazz = mustStatic ? locationClass[0] : inv.getTarget().getClass();
191
192        MethodWrapper m = ResourceMetadataRegistry.lookupDefaultFallback(clazz, defaultFallback);
193        if (m == null) {
194            // First time, resolve the default fallback.
195            Class<?> originReturnType = resolveMethod(inv).getReturnType();
196            // Default fallback allows two kinds of parameter list.
197            // One is empty parameter list.
198            Class<?>[] defaultParamTypes = new Class<?>[0];
199            // The other is a single parameter {@link Throwable} to get relevant exception info.
200            Class<?>[] paramTypeWithException = new Class<?>[]{Throwable.class};
201            // We first find the default fallback with empty parameter list.
202            Method method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, defaultParamTypes);
203            // If default fallback with empty params is absent, we then try to find the other one.
204            if (method == null) {
205                method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, paramTypeWithException);
206            }
207            // Cache the method instance.
208            ResourceMetadataRegistry.updateDefaultFallbackFor(clazz, defaultFallback, method);
209            return method;
210        }
211        if (!m.isPresent()) {
212            return null;
213        }
214        return m.getMethod();
215    }
216
217    private Method resolveFallbackInternal(Invocation inv, /*@NonNull*/ String name, Class<?> clazz,
218                                           boolean mustStatic) {
219        Method originMethod = resolveMethod(inv);
220        // Fallback function allows two kinds of parameter list.
221        Class<?>[] defaultParamTypes = originMethod.getParameterTypes();
222        Class<?>[] paramTypesWithException = Arrays.copyOf(defaultParamTypes, defaultParamTypes.length + 1);
223        paramTypesWithException[paramTypesWithException.length - 1] = Throwable.class;
224        // We first find the fallback matching the signature of origin method.
225        Method method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), defaultParamTypes);
226        // If fallback matching the origin method is absent, we then try to find the other one.
227        if (method == null) {
228            method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), paramTypesWithException);
229        }
230        return method;
231    }
232
233    private Method extractBlockHandlerMethod(Invocation inv, String name, Class<?>[] locationClass) {
234        if (StringUtil.isBlank(name)) {
235            return null;
236        }
237
238        boolean mustStatic = locationClass != null && locationClass.length >= 1;
239        Class<?> clazz;
240        if (mustStatic) {
241            clazz = locationClass[0];
242        } else {
243            // By default current class.
244            clazz = inv.getTarget().getClass();
245        }
246        MethodWrapper m = ResourceMetadataRegistry.lookupBlockHandler(clazz, name);
247        if (m == null) {
248            // First time, resolve the block handler.
249            Method method = resolveBlockHandlerInternal(inv, name, clazz, mustStatic);
250            // Cache the method instance.
251            ResourceMetadataRegistry.updateBlockHandlerFor(clazz, name, method);
252            return method;
253        }
254        if (!m.isPresent()) {
255            return null;
256        }
257        return m.getMethod();
258    }
259
260    private Method resolveBlockHandlerInternal(Invocation inv, /*@NonNull*/ String name, Class<?> clazz,
261                                               boolean mustStatic) {
262        Method originMethod = resolveMethod(inv);
263        Class<?>[] originList = originMethod.getParameterTypes();
264        Class<?>[] parameterTypes = Arrays.copyOf(originList, originList.length + 1);
265        parameterTypes[parameterTypes.length - 1] = BlockException.class;
266        return findMethod(mustStatic, clazz, name, originMethod.getReturnType(), parameterTypes);
267    }
268
269    private boolean checkStatic(boolean mustStatic, Method method) {
270        return !mustStatic || isStatic(method);
271    }
272
273    private Method findMethod(boolean mustStatic, Class<?> clazz, String name, Class<?> returnType,
274                              Class<?>... parameterTypes) {
275        Method[] methods = clazz.getDeclaredMethods();
276        for (Method method : methods) {
277            if (name.equals(method.getName()) && checkStatic(mustStatic, method)
278                    && returnType.isAssignableFrom(method.getReturnType())
279                    && Arrays.equals(parameterTypes, method.getParameterTypes())) {
280
281                RecordLog.info("Resolved method [{0}] in class [{1}]", name, clazz.getCanonicalName());
282                return method;
283            }
284        }
285        // Current class not found, find in the super classes recursively.
286        Class<?> superClass = clazz.getSuperclass();
287        if (superClass != null && !Object.class.equals(superClass)) {
288            return findMethod(mustStatic, superClass, name, returnType, parameterTypes);
289        } else {
290            String methodType = mustStatic ? " static" : "";
291            RecordLog.warn("Cannot find{0} method [{1}] in class [{2}] with parameters {3}",
292                    methodType, name, clazz.getCanonicalName(), Arrays.toString(parameterTypes));
293            return null;
294        }
295    }
296
297    private boolean isStatic(Method method) {
298        return Modifier.isStatic(method.getModifiers());
299    }
300
301    protected Method resolveMethod(Invocation inv) {
302        return inv.getMethod();
303    }
304
305}