/* * Copyright 2012-2017 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 org.springframework.boot.autoconfigure.web.servlet.error; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; /** * Default implementation of {@link ErrorAttributes}. Provides the following attributes * when possible: * <ul> * <li>timestamp - The time that the errors were extracted</li> * <li>status - The status code</li> * <li>error - The error reason</li> * <li>exception - The class name of the root exception (if configured)</li> * <li>message - The exception message</li> * <li>errors - Any {@link ObjectError}s from a {@link BindingResult} exception * <li>trace - The exception stack trace</li> * <li>path - The URL path when the exception was raised</li> * </ul> * * @author Phillip Webb * @author Dave Syer * @author Stephane Nicoll * @author Vedran Pavic * @since 1.1.0 * @see ErrorAttributes */ @Order(Ordered.HIGHEST_PRECEDENCE) public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered { private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR"; private final boolean includeException; /** * Create a new {@link DefaultErrorAttributes} instance that does not include the * "exception" attribute. */ public DefaultErrorAttributes() { this(false); } /** * Create a new {@link DefaultErrorAttributes} instance. * @param includeException whether to include the "exception" attribute */ public DefaultErrorAttributes(boolean includeException) { this.includeException = includeException; } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { storeErrorAttributes(request, ex); return null; } private void storeErrorAttributes(HttpServletRequest request, Exception ex) { request.setAttribute(ERROR_ATTRIBUTE, ex); } @Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, requestAttributes); addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); addPath(errorAttributes, requestAttributes); return errorAttributes; } private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) { Integer status = getAttribute(requestAttributes, "javax.servlet.error.status_code"); if (status == null) { errorAttributes.put("status", 999); errorAttributes.put("error", "None"); return; } errorAttributes.put("status", status); try { errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase()); } catch (Exception ex) { // Unable to obtain a reason errorAttributes.put("error", "Http Status " + status); } } private void addErrorDetails(Map<String, Object> errorAttributes, RequestAttributes requestAttributes, boolean includeStackTrace) { Throwable error = getError(requestAttributes); if (error != null) { while (error instanceof ServletException && error.getCause() != null) { error = ((ServletException) error).getCause(); } if (this.includeException) { errorAttributes.put("exception", error.getClass().getName()); } addErrorMessage(errorAttributes, error); if (includeStackTrace) { addStackTrace(errorAttributes, error); } } Object message = getAttribute(requestAttributes, "javax.servlet.error.message"); if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null) && !(error instanceof BindingResult)) { errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message); } } private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) { BindingResult result = extractBindingResult(error); if (result == null) { errorAttributes.put("message", error.getMessage()); return; } if (result.getErrorCount() > 0) { errorAttributes.put("errors", result.getAllErrors()); errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. Error count: " + result.getErrorCount()); } else { errorAttributes.put("message", "No errors"); } } private BindingResult extractBindingResult(Throwable error) { if (error instanceof BindingResult) { return (BindingResult) error; } if (error instanceof MethodArgumentNotValidException) { return ((MethodArgumentNotValidException) error).getBindingResult(); } return null; } private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) { StringWriter stackTrace = new StringWriter(); error.printStackTrace(new PrintWriter(stackTrace)); stackTrace.flush(); errorAttributes.put("trace", stackTrace.toString()); } private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) { String path = getAttribute(requestAttributes, "javax.servlet.error.request_uri"); if (path != null) { errorAttributes.put("path", path); } } @Override public Throwable getError(RequestAttributes requestAttributes) { Throwable exception = getAttribute(requestAttributes, ERROR_ATTRIBUTE); if (exception == null) { exception = getAttribute(requestAttributes, "javax.servlet.error.exception"); } return exception; } @SuppressWarnings("unchecked") private <T> T getAttribute(RequestAttributes requestAttributes, String name) { return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST); } }