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.web.handler;
017
018import com.jfinal.aop.Interceptor;
019import com.jfinal.aop.Invocation;
020import com.jfinal.core.Action;
021import com.jfinal.core.ActionReporter;
022import com.jfinal.core.Controller;
023import com.jfinal.core.JFinal;
024import com.jfinal.kit.JsonKit;
025import com.jfinal.render.*;
026import io.jboot.Jboot;
027import io.jboot.JbootConsts;
028import io.jboot.support.jwt.JwtInterceptor;
029import io.jboot.utils.ClassUtil;
030import io.jboot.utils.ReflectUtil;
031import io.jboot.utils.RequestUtil;
032import io.jboot.utils.StrUtil;
033import io.jboot.web.controller.JbootController;
034import io.jboot.web.render.JbootReturnValueRender;
035import javassist.*;
036
037import javax.servlet.http.HttpServletRequest;
038import java.io.File;
039import java.io.IOException;
040import java.io.Writer;
041import java.text.SimpleDateFormat;
042import java.util.Date;
043import java.util.Enumeration;
044import java.util.List;
045
046
047/**
048 * JbootActionReporter 参考 ActionReporter
049 */
050public class JbootActionReporter {
051
052    private static final String title = "\nJboot-" + JbootConsts.VERSION + " action report -------- ";
053    private static final String interceptMethodDesc = "(Lcom/jfinal/aop/Invocation;)V";
054    private static int maxOutputLengthOfParaValue = 512;
055    private static Writer writer = new SystemOutWriter();
056    private static ActionReporter actionReporter = JFinal.me().getConstants().getActionReporter();
057    private static boolean reportEnable = Jboot.isDevMode();
058    private static boolean colorRenderEnable = true;
059    private static boolean reportAllText = false;
060
061    private static final ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
062
063
064    public static void setMaxOutputLengthOfParaValue(int maxOutputLengthOfParaValue) {
065        if (maxOutputLengthOfParaValue < 16) {
066            throw new IllegalArgumentException("maxOutputLengthOfParaValue must more than 16");
067        }
068        JbootActionReporter.maxOutputLengthOfParaValue = maxOutputLengthOfParaValue;
069    }
070
071    public static void setWriter(Writer writer) {
072        if (writer == null) {
073            throw new IllegalArgumentException("writer can not be null");
074        }
075        JbootActionReporter.writer = writer;
076    }
077
078    public static Writer getWriter() {
079        return writer;
080    }
081
082    public static boolean isReportEnable() {
083        return reportEnable;
084    }
085
086    public static void setReportEnable(boolean reportEnable) {
087        JbootActionReporter.reportEnable = reportEnable;
088    }
089
090    public static boolean isReportAllText() {
091        return reportAllText;
092    }
093
094    public static void setReportAllText(boolean reportAllText) {
095        JbootActionReporter.reportAllText = reportAllText;
096    }
097
098    public static boolean isColorRenderEnable() {
099        return colorRenderEnable;
100    }
101
102    public static void setColorRenderEnable(boolean colorRenderEnable) {
103        JbootActionReporter.colorRenderEnable = colorRenderEnable;
104    }
105
106    /**
107     * Report the action
108     */
109    public static void report(String target, Controller controller, Action action, Invocation invocation, long time) {
110        try {
111            doReport(target, controller, action, invocation, time);
112        }
113        // 在 tomcat 或者自定义 classloader 的情况下,
114        // 可能会出现 NotFoundException 错误
115        catch (NotFoundException e) {
116            ClassPool.getDefault().insertClassPath(new ClassClassPath(controller.getClass()));
117            try {
118                doReport(target, controller, action, invocation, time);
119            } catch (Exception exception) {
120                actionReporter.report(target, controller, action);
121            }
122        } catch (Exception ex) {
123            actionReporter.report(target, controller, action);
124        } finally {
125            JbootActionReporterInvocation.clear();
126        }
127    }
128
129
130    private static void doReport(String target, Controller controller, Action action, Invocation invocation, long time) throws Exception {
131        CtClass ctClass = ClassPool.getDefault().get(ClassUtil.getUsefulClass(action.getControllerClass()).getName());
132        ClassPool.getDefault().get(ClassUtil.getUsefulClass(action.getControllerClass()).getName());
133        String desc = JbootActionReporterUtil.getMethodDescWithoutName(action.getMethod());
134        CtMethod ctMethod = ctClass.getMethod(action.getMethodName(), desc);
135        int lineNumber = ctMethod.getMethodInfo().getLineNumber(0);
136
137        StringBuilder sb = new StringBuilder(title).append(sdf.get().format(new Date(time))).append(" -------------------------\n");
138        sb.append("Request     : ").append(controller.getRequest().getMethod()).append(" ").append(target).append("\n");
139        Class<?> cc = action.getMethod().getDeclaringClass();
140        sb.append("Controller  : ").append(cc.getName()).append(".(").append(getClassFileName(cc)).append(".java:" + lineNumber + ")");
141        if (JbootActionReporterInvocation.isControllerInvoked()) {
142            sb.append((colorRenderEnable ? ConsoleColor.GREEN_BRIGHT : "") + " ---> invoked √" + (colorRenderEnable ? ConsoleColor.RESET : ""));
143        } else {
144            sb.append((colorRenderEnable ? ConsoleColor.RED_BRIGHT : "") + " ---> skipped ×" + (colorRenderEnable ? ConsoleColor.RESET : ""));
145        }
146        sb.append("\nMethod      : ").append(JbootActionReporterUtil.getMethodString(action.getMethod())).append("\n");
147
148
149        String urlParas = controller.getPara();
150        if (urlParas != null) {
151            sb.append("UrlPara     : ").append(urlParas).append("\n");
152        }
153
154        Interceptor[] inters = invocation instanceof JbootActionReporterInvocation ? ((JbootActionReporterInvocation) invocation).getInters() : action.getInterceptors();
155        List<Interceptor> invokedInterceptors = JbootActionReporterInvocation.getInvokedInterceptor();
156
157        boolean printJwt = false;
158
159        if (inters.length > 0) {
160            sb.append("Interceptor : ");
161            for (int i = 0; i < inters.length; i++) {
162                if (i > 0) {
163                    sb.append("\n              ");
164                }
165                Interceptor inter = inters[i];
166                Class<?> interClass = ClassUtil.getUsefulClass(inter.getClass());
167
168                if (interClass == JwtInterceptor.class) {
169                    printJwt = true;
170                }
171
172                CtClass icClass = ClassPool.getDefault().get(interClass.getName());
173                CtMethod icMethod = icClass.getMethod("intercept", interceptMethodDesc);
174                int icLineNumber = icMethod.getMethodInfo().getLineNumber(0);
175                sb.append(icMethod.getDeclaringClass().getName()).append(".(").append(getClassFileName(interClass)).append(".java:" + icLineNumber + ")");
176
177                if (invokedInterceptors.contains(inter)) {
178                    sb.append((colorRenderEnable ? ConsoleColor.GREEN_BRIGHT : "") + " ---> invoked √" + (colorRenderEnable ? ConsoleColor.RESET : ""));
179                } else {
180                    sb.append((colorRenderEnable ? ConsoleColor.RED_BRIGHT : "") + " ---> skipped ×" + (colorRenderEnable ? ConsoleColor.RESET : ""));
181                }
182            }
183            sb.append("\n");
184        }
185
186        // print all parameters
187        HttpServletRequest request = controller.getRequest();
188        Enumeration<String> e = request.getParameterNames();
189        if (e.hasMoreElements()) {
190            sb.append("Parameter   : ");
191            while (e.hasMoreElements()) {
192                String name = e.nextElement();
193                String[] values = request.getParameterValues(name);
194                if (values.length == 1) {
195                    sb.append(name).append("=");
196                    if (values[0] != null && values[0].length() > maxOutputLengthOfParaValue) {
197                        sb.append(values[0], 0, maxOutputLengthOfParaValue).append("...");
198                    } else {
199                        sb.append(values[0]);
200                    }
201                } else {
202                    sb.append(name).append("[]={");
203                    for (int i = 0; i < values.length; i++) {
204                        if (i > 0) {
205                            sb.append(",");
206                        }
207                        sb.append(values[i]);
208                    }
209                    sb.append("}");
210                }
211                sb.append("  ");
212            }
213            sb.append("\n");
214        }
215
216
217        if (!"GET".equalsIgnoreCase(controller.getRequest().getMethod())
218                && !RequestUtil.isMultipartRequest(controller.getRequest())
219                && StrUtil.isNotBlank(controller.getRawData())) {
220            sb.append("RawData     : ").append(controller.getRawData());
221            sb.append("\n");
222        }
223
224        if (printJwt && controller instanceof JbootController) {
225            String jwtString = JsonKit.toJson(((JbootController) controller).getJwtParas());
226            if (StrUtil.isNotBlank(jwtString)) {
227                sb.append("Jwt         : ").append(jwtString.replace("\n", ""));
228                sb.append("\n");
229            }
230        }
231
232        appendRenderMessage(controller.getRender(), sb);
233
234        sb.append("----------------------------------- took " + (System.currentTimeMillis() - time) + " ms --------------------------------\n\n\n");
235
236        writer.write(sb.toString());
237    }
238
239    private static void appendRenderMessage(Render render, StringBuilder sb) {
240        if (render == null) {
241            return;
242        }
243        String view = render.getView();
244        if (StrUtil.isNotBlank(view)) {
245            sb.append("Render      : ").append(view);
246        } else if (render instanceof JsonRender) {
247            String jsontext = ((JsonRender) render).getJsonText();
248            if (jsontext == null) {
249                jsontext = "";
250            }
251            jsontext = jsontext.replace("\n", "");
252            sb.append("Render      : ").append(getRenderText(jsontext));
253        } else if (render instanceof TextRender) {
254            String text = ((TextRender) render).getText();
255            if (text == null) {
256                text = "";
257            }
258            text = text.replace("\n", "");
259            sb.append("Render      : ").append(getRenderText(text));
260        } else if (render instanceof FileRender) {
261            File file = ReflectUtil.getFieldValue(render, "file");
262            sb.append("Render      : ").append(file);
263        } else if (render instanceof RedirectRender) {
264            String url = ReflectUtil.getFieldValue(render, "url");
265            sb.append("Redirect    : ").append(url);
266        } else if (render instanceof NullRender) {
267            sb.append("Render      :  null");
268        } else if (render instanceof JbootReturnValueRender) {
269            appendRenderMessage(((JbootReturnValueRender) render).getRealRender(), sb);
270        } else {
271            sb.append("Render      : ").append(ClassUtil.getUsefulClass(render.getClass()).getName());
272        }
273        sb.append("\n");
274    }
275
276
277    private static String getRenderText(String orignalText) {
278        if (StrUtil.isBlank(orignalText)) {
279            return "";
280        }
281
282        if (!reportAllText && orignalText.length() > 100) {
283            return orignalText.substring(0, 100) + "...";
284        }
285
286        return orignalText;
287    }
288
289
290    private static String getClassFileName(Class<?> clazz) {
291        String classFileName = clazz.getName();
292        if (classFileName.contains("$")) {
293            int indexOf = classFileName.contains(".") ? classFileName.lastIndexOf(".") + 1 : 0;
294            return classFileName.substring(indexOf, classFileName.indexOf("$"));
295        } else {
296            return clazz.getSimpleName();
297        }
298    }
299
300
301    private static class SystemOutWriter extends Writer {
302        @Override
303        public void write(String str) throws IOException {
304            System.out.print(str);
305        }
306
307        @Override
308        public void write(char[] cbuf, int off, int len) throws IOException {
309        }
310
311        @Override
312        public void flush() throws IOException {
313        }
314
315        @Override
316        public void close() throws IOException {
317        }
318    }
319}
320
321