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.Invocation;
019import com.jfinal.core.*;
020import com.jfinal.log.Log;
021import com.jfinal.render.IRenderFactory;
022import com.jfinal.render.Render;
023import com.jfinal.render.RenderException;
024import com.jfinal.template.TemplateException;
025import io.jboot.app.JbootApplicationConfig;
026import io.jboot.components.valid.ValidErrorRender;
027import io.jboot.components.valid.ValidException;
028import io.jboot.components.valid.ValidUtil;
029import io.jboot.utils.ClassUtil;
030import io.jboot.web.controller.JbootControllerContext;
031import io.jboot.web.render.JbootErrorRender;
032import io.jboot.web.render.JbootRenderFactory;
033import io.jboot.web.render.JbootReturnValueRender;
034
035import javax.servlet.http.HttpServletRequest;
036import javax.servlet.http.HttpServletResponse;
037
038/**
039 * @author Michael Yang 杨福海 (fuhai999@gmail.com)
040 * @version V1.0
041 */
042public class JbootActionHandler extends ActionHandler {
043
044
045    private static final Log LOG = Log.getLog(JbootActionHandler.class);
046    private static final JbootApplicationConfig appConfig = JbootApplicationConfig.get();
047
048    /**
049     * 方便子类复写、从而可以实现 自定义 Action 的功能
050     *
051     * @param target
052     * @param urlPara
053     * @param request
054     * @return
055     */
056    public Action getAction(String target, String[] urlPara, HttpServletRequest request) {
057        return this.getAction(target, urlPara);
058    }
059
060
061    /**
062     * 方便子类复写、从而可以实现 自定义 Action 的功能
063     *
064     * @param target
065     * @param urlPara
066     * @return
067     */
068    @Override
069    protected Action getAction(String target, String[] urlPara) {
070        return super.getAction(target, urlPara);
071    }
072
073    /**
074     * 方便子类复写、从而可以实现 自定义 Invocation 的功能
075     *
076     * @param action
077     * @param controller
078     * @return
079     */
080    public Invocation getInvocation(Action action, Controller controller) {
081        return JbootActionReporter.isReportEnable() ? new JbootActionReporterInvocation(action, controller) : new JbootActionInvocation(action, controller);
082    }
083
084
085    /**
086     * handle
087     * 1: Action action = actionMapping.getAction(target)
088     * 2: new Invocation(...).invoke()
089     * 3: render(...)
090     */
091    @Override
092    public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
093        if (target.lastIndexOf('.') != -1) {
094            return;
095        }
096
097        isHandled[0] = true;
098        String[] urlPara = {null};
099        Action action = getAction(target, urlPara, request);
100
101        if (action == null) {
102            if (!appConfig.isHandle404()) {
103                isHandled[0] = false;
104                return;
105            }
106
107            if (LOG.isWarnEnabled()) {
108                String qs = request.getQueryString();
109                LOG.warn("404 Action Not Found: " + (qs == null ? target : target + "?" + qs));
110            }
111            renderManager.getRenderFactory().getErrorRender(404).setContext(request, response).render();
112            return;
113        }
114        Controller controller = null;
115        try {
116            controller = controllerFactory.getController(action.getControllerClass());
117            //controller.init(request, response, urlPara[0]);
118            CPI._init_(controller, action, request, response, urlPara[0]);
119
120            JbootControllerContext.hold(controller);
121
122            //Invocation invocation = new Invocation(action, controller);
123            Invocation invocation = getInvocation(action, controller);
124
125            if (JbootActionReporter.isReportEnable()) {
126                long time = System.currentTimeMillis();
127                try {
128                    doStartRender(target, action, controller, invocation, isHandled);
129                } finally {
130                    JbootActionReporter.report(target, controller, action, invocation, time);
131                }
132            } else {
133                doStartRender(target, action, controller, invocation, isHandled);
134            }
135
136            doAfterRender(action, controller);
137
138        } catch (RenderException e) {
139            if (LOG.isErrorEnabled()) {
140                String qs = request.getQueryString();
141                LOG.error(qs == null ? target : target + "?" + qs, e);
142            }
143        } catch (ActionException e) {
144            if (e.getErrorCode() == 404 && !appConfig.isHandle404()) {
145                isHandled[0] = false;
146                return;
147            }
148            handleActionException(target, request, response, action, e);
149        } catch (ValidException e) {
150            handleValidException(target, request, response, action, e);
151        } catch (TemplateException e) {
152            handleTemplateException(target, request, response, action, e);
153        } catch (Exception e) {
154            handleException(target, request, response, action, e);
155        } finally {
156            JbootControllerContext.release();
157            controllerFactory.recycle(controller);
158        }
159    }
160
161    protected boolean isJspTarget(String target) {
162        return target.toLowerCase().contains(".jsp");
163    }
164
165
166    protected void doAfterRender(Action action, Controller controller) {
167    }
168
169
170    protected void doStartRender(String target
171            , Action action
172            , Controller controller
173            , Invocation invocation
174            , boolean[] isHandled) {
175
176        invocation.invoke();
177
178        Render render = controller.getRender();
179        if (render instanceof ForwardActionRender) {
180            String actionUrl = ((ForwardActionRender) render).getActionUrl();
181            if (target.equals(actionUrl)) {
182                throw new RuntimeException("The forward action url is the same as before.");
183            } else {
184                handle(actionUrl, controller.getRequest(), controller.getResponse(), isHandled);
185            }
186        } else {
187            if (render == null && void.class != action.getMethod().getReturnType()
188                    && renderManager.getRenderFactory() instanceof JbootRenderFactory) {
189
190                JbootRenderFactory factory = (JbootRenderFactory) renderManager.getRenderFactory();
191                JbootReturnValueRender returnValueRender = factory.getReturnValueRender(invocation.getReturnValue());
192
193                String forwardTo = returnValueRender.getForwardTo();
194                if (forwardTo != null) {
195                    handle(getRealForwrdTo(forwardTo, target, action), controller.getRequest(), controller.getResponse(), isHandled);
196                    return;
197                } else {
198                    render = returnValueRender;
199                    //重新设置到 Controller,JbootActionReporter 才能 Controller 获取 render 判断 render 类型
200                    controller.render(render);
201                }
202            }
203
204            if (render == null) {
205                render = renderManager.getRenderFactory().getDefaultRender(action.getViewPath() + action.getMethodName());
206
207                //重新设置到 Controller,JbootActionReporter 才能 Controller 获取 render 判断 render 类型
208                controller.render(render);
209            }
210
211            render.setContext(controller.getRequest(), controller.getResponse(), action.getViewPath()).render();
212        }
213    }
214
215    public String getRealForwrdTo(String forwardTo, String currentTarget, Action action) {
216        if ("".equals(forwardTo)) {
217            throw new IllegalArgumentException(ClassUtil.buildMethodString(action.getMethod()) + ": The forward key can not be blank.");
218        }
219
220        if (forwardTo.startsWith("/")) {
221            return forwardTo;
222        }
223
224
225        if (forwardTo.startsWith("./")) {
226            return currentTarget.substring(0, currentTarget.lastIndexOf("/")) + forwardTo.substring(1);
227        }
228
229        return "/" + forwardTo;
230    }
231
232
233    /**
234     * 处理错误信息
235     *
236     * @param target
237     * @param request
238     * @param response
239     * @param action
240     * @param e
241     */
242    protected void handleActionException(String target, HttpServletRequest request, HttpServletResponse response, Action action, ActionException e) {
243        int errorCode = e.getErrorCode();
244        String msg = null;
245        if (errorCode == 404) {
246            msg = "404 Not Found: ";
247        } else if (errorCode == 400) {
248            msg = "400 Bad Request: ";
249        } else if (errorCode == 401) {
250            msg = "401 Unauthorized: ";
251        } else if (errorCode == 403) {
252            msg = "403 Forbidden: ";
253        }
254
255
256        if (msg != null) {
257            if (errorCode == 404 || errorCode == 401 || errorCode == 403) {
258                if (LOG.isWarnEnabled()) {
259                    String qs = request.getQueryString();
260                    msg = msg + (qs == null ? target : target + "?" + qs);
261                    LOG.info(msg, e);
262                }
263            } else {
264                if (LOG.isErrorEnabled()) {
265                    String qs = request.getQueryString();
266                    msg = msg + (qs == null ? target : target + "?" + qs);
267                    LOG.error(msg, e);
268                }
269            }
270        } else {
271            if (LOG.isErrorEnabled()) {
272                String qs = request.getQueryString();
273                LOG.error(errorCode + " Error: " + (qs == null ? target : target + "?" + qs), e);
274            }
275        }
276
277        e.getErrorRender().setContext(request, response, action.getViewPath()).render();
278    }
279
280
281    /**
282     * 处理参数验证错误
283     */
284    protected void handleValidException(String target, HttpServletRequest request, HttpServletResponse response, Action action, ValidException validException) {
285        if (LOG.isErrorEnabled()) {
286//            String qs = request.getQueryString();
287//            String targetInfo = qs == null ? target : target + "?" + qs;
288//            LOG.error(validException.getReason() + " : " + targetInfo, validException);
289            LOG.error("Invalid parameter: " + validException.getReason());
290        }
291        IRenderFactory factory = renderManager.getRenderFactory();
292        if (factory instanceof JbootRenderFactory) {
293            ValidErrorRender render = ((JbootRenderFactory) factory).getValidErrorRender(validException);
294            render.setContext(request, response, action.getViewPath()).render();
295        } else {
296            Render render = renderManager.getRenderFactory().getErrorRender(ValidUtil.getErrorCode());
297            if (render instanceof JbootErrorRender) {
298                ((JbootErrorRender) render).setThrowable(validException);
299            }
300            render.setContext(request, response, action.getViewPath()).render();
301        }
302    }
303
304
305    /**
306     * 处理模板错误
307     */
308    protected void handleTemplateException(String target, HttpServletRequest request, HttpServletResponse response, Action action, TemplateException e) {
309        String qs = request.getQueryString();
310        String targetInfo = qs == null ? target : target + "?" + qs;
311        String info = ClassUtil.buildMethodString(action.getMethod());
312        LOG.error(info + " \nQuery: " + targetInfo + "\n", e);
313
314        IRenderFactory factory = renderManager.getRenderFactory();
315        if (factory instanceof JbootRenderFactory) {
316            ((JbootRenderFactory) factory).getTemplateErrorRender(e).setContext(request, response, action.getViewPath()).render();
317        }
318    }
319
320
321    /**
322     * 处理其他业务错误
323     */
324    protected void handleException(String target, HttpServletRequest request, HttpServletResponse response, Action action, Exception e) {
325        String qs = request.getQueryString();
326        String targetInfo = qs == null ? target : target + "?" + qs;
327        String info = ClassUtil.buildMethodString(action.getMethod());
328        LOG.error(info + " \nQuery: " + targetInfo + "\n", e);
329        renderManager.getRenderFactory().getErrorRender(500).setContext(request, response, action.getViewPath()).render();
330    }
331
332
333}