content: /*
   * Copyright (C) 2018 the original author or authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.alibaba.codeless.framework.web;
  
  import com.alibaba.codeless.framework.core.utils.JsonUtils;
  import com.alibaba.codeless.framework.core.utils.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.context.MessageSource;
  import org.springframework.context.NoSuchMessageException;
  import org.springframework.core.Ordered;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.method.HandlerMethod;
  import org.springframework.web.servlet.HandlerExceptionResolver;
  import org.springframework.web.servlet.ModelAndView;
  import org.springframework.web.servlet.View;
  import org.springframework.web.servlet.i18n.SessionLocaleResolver;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import javax.validation.ConstraintViolation;
  import javax.validation.ConstraintViolationException;
  import java.util.*;
  
  /**
   * @author xiaolongzuo
   */
  public class JsonHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
  
      public static final Logger log = LoggerFactory.getLogger(JsonHandlerExceptionResolver.class);
  
      private static final String KEY = "exception";
  
      private View errorView = new ErrorView();
  
      private MessageSource messageSource;
  
      public JsonHandlerExceptionResolver(MessageSource messageSource) {
          this.messageSource = messageSource;
      }
  
      @Override
      public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
                                           Exception exception) {
          ExceptionMetadata exceptionMetadata = logException(handler, request, exception);
          JsonResponse result = buildJsonResponse(request, exception, exceptionMetadata.getErrorId());
          ModelAndView view = new ModelAndView(errorView);
          view.addObject(KEY, result);
          return view;
      }
  
      private JsonResponse buildJsonResponse(HttpServletRequest request, Throwable throwable, String errorId) {
          while (throwable.getCause() != null) {
              throwable = throwable.getCause();
          }
          String code;
          Object[] args = new Object[0];
          Object data = null;
          if (throwable instanceof ConstraintViolationException) {
              ConstraintViolationException constraintViolationException = (ConstraintViolationException)throwable;
              code = "YA-400";
              data = new ArrayList<>();
              for (ConstraintViolation<?> constraintViolation : constraintViolationException.getConstraintViolations()) {
                  Map<String, String> map = new HashMap<>();
                  map.put("message", constraintViolation.getMessage());
                  map.put("propertyPath", constraintViolation.getPropertyPath().toString());
                  ((List)data).add(map);
              }
          } else if (throwable instanceof BusinessException) {
              BusinessException businessException = (BusinessException)throwable;
              code = businessException.getCode();
              args = businessException.getArgs();
              if (request.getSession() == null) {
                  code = "YA-400";
              }
          } else {
              code = "YA-500";
              args = new Object[] {errorId};
          }
          Locale locale = (Locale)request.getSession().getAttribute(
              SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME);
          String message;
          try {
              message = messageSource.getMessage(code, args, locale);
          } catch (NoSuchMessageException e) {
              if (DefaultMessagesProperties.contains(code)) {
                  message = DefaultMessagesProperties.getMessage(code, args);
              } else if (throwable instanceof BusinessException && throwable.getMessage() != null) {
                  message = throwable.getMessage();
              } else {
                  throw e;
              }
          }
          return new JsonResponse(code, message, data);
      }
  
      private ExceptionMetadata logException(Object handler, HttpServletRequest request, Exception exception) {
          ExceptionMetadata exceptionMetadata = new ExceptionMetadata();
          try {
              buildExceptionMetadata(exceptionMetadata, handler, request);
              if (exceptionMetadata.isHandlerMethod()) {
                  Logger logger = LoggerFactory.getLogger(exceptionMetadata.getBeanType());
                  logger.error("Error id is :" + exceptionMetadata.getErrorId());
                  logger.error("RequestMapping is:");
                  logger.error(exceptionMetadata.getRequestMapping());
                  logger.error("HandlerMethod is:");
                  logger.error(exceptionMetadata.getMethodName());
                  logger.error("ParameterMap is:");
                  logger.error(exceptionMetadata.getParameterMap(), exception);
              } else {
                  log.error(handler + " execute failed, error id is:" + exceptionMetadata.getErrorId(), exception);
              }
          } catch (Exception e) {
              log.error(handler + " execute failed.", exception);
          }
          return exceptionMetadata;
      }
  
      private void buildExceptionMetadata(ExceptionMetadata exceptionMetadata, Object handler,
                                          HttpServletRequest request) {
          if (handler != null && HandlerMethod.class.isAssignableFrom(handler.getClass())) {
              HandlerMethod handlerMethod = (HandlerMethod)handler;
              Class<?> beanType = handlerMethod.getBeanType();
              String methodName = handlerMethod.getMethod().getName();
              RequestMapping controllerRequestMapping = beanType.getDeclaredAnnotation(RequestMapping.class);
              String classMapping = "";
              if (controllerRequestMapping != null) {
                  classMapping = controllerRequestMapping.value()[0];
              }
              RequestMapping methodRequestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
              String methodMapping = "";
              if (methodRequestMapping != null) {
                  methodMapping = methodRequestMapping.value()[0];
              }
              if (!methodMapping.startsWith("/")) {
                  methodMapping = "/" + methodMapping;
              }
              exceptionMetadata.setHandlerMethod(true);
              exceptionMetadata.setBeanType(beanType);
              exceptionMetadata.setMethodName(methodName);
              exceptionMetadata.setClassMapping(classMapping);
              exceptionMetadata.setMethodMapping(methodMapping);
              exceptionMetadata.setParameterMap(request.getParameterMap());
          } else {
              exceptionMetadata.setHandlerMethod(false);
          }
      }
  
      @Override
      public int getOrder() {
          return -10;
      }
  
      private static class ExceptionMetadata {
  
          private String errorId = UUID.randomUUID().toString();
  
          private String methodName;
  
          private String classMapping;
  
          private String methodMapping;
  
          private Class<?> beanType;
  
          private Map<String, String[]> parameterMap;
  
          private boolean isHandlerMethod;
  
          public void setMethodName(String methodName) {
              this.methodName = methodName;
          }
  
          public void setClassMapping(String classMapping) {
              this.classMapping = classMapping;
          }
  
          public void setMethodMapping(String methodMapping) {
              this.methodMapping = methodMapping;
          }
  
          public void setBeanType(Class<?> beanType) {
              this.beanType = beanType;
          }
  
          public void setParameterMap(Map<String, String[]> parameterMap) {
              this.parameterMap = parameterMap;
          }
  
          public boolean isHandlerMethod() {
              return isHandlerMethod;
          }
  
          public void setHandlerMethod(boolean handlerMethod) {
              isHandlerMethod = handlerMethod;
          }
  
          public String getErrorId() {
              return errorId;
          }
  
          public String getRequestMapping() {
              return classMapping + methodMapping;
          }
  
          public String getMethodName() {
              return getBeanType().getSimpleName() + "." + methodName + "()";
          }
  
          public Class<?> getBeanType() {
              return beanType;
          }
  
          public String getParameterMap() {
              return JsonUtils.toJson(parameterMap);
          }
      }
  
      private static class ErrorView implements View {
  
          private static final String CONTENT_TYPE = "application/json;charset=utf-8";
  
          @Override
          public String getContentType() {
              return CONTENT_TYPE;
          }
  
          @Override
          public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
              throws Exception {
              JsonResponse errorResponse = (JsonResponse)model.get(KEY);
              response.setContentType(CONTENT_TYPE);
              response.getOutputStream().write(StringUtils.toBytes(JsonUtils.toJson(errorResponse)));
              response.getOutputStream().flush();
          }
  
      }
  
  }
  