content: package me.alidg.errors.conf;
  
  import me.alidg.errors.WebErrorHandlers;
  import me.alidg.errors.adapter.HttpErrorAttributesAdapter;
  import me.alidg.errors.adapter.attributes.ServletErrorAttributes;
  import me.alidg.errors.mvc.ErrorsControllerAdvice;
  import org.springframework.beans.factory.ObjectProvider;
  import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  import org.springframework.boot.autoconfigure.AutoConfigureBefore;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
  import org.springframework.boot.autoconfigure.web.ErrorProperties;
  import org.springframework.boot.autoconfigure.web.ServerProperties;
  import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
  import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
  import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
  import org.springframework.boot.web.servlet.error.ErrorAttributes;
  import org.springframework.boot.web.servlet.error.ErrorController;
  import org.springframework.context.annotation.Bean;
  import org.springframework.http.HttpStatus;
  import org.springframework.http.ResponseEntity;
  import org.springframework.web.bind.annotation.RequestMapping;
  
  import javax.servlet.http.HttpServletRequest;
  import java.util.List;
  import java.util.Map;
  
  import static java.util.stream.Collectors.toList;
  import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;
  import static org.springframework.http.MediaType.ALL;
  
  /**
   * Encapsulates servlet-specific parts of errors auto-configuration.
   *
   * @author Ali Dehghani
   */
  @ConditionalOnWebApplication(type = SERVLET)
  @AutoConfigureAfter(ErrorsAutoConfiguration.class)
  @AutoConfigureBefore(ErrorMvcAutoConfiguration.class)
  public class ServletErrorsAutoConfiguration {
  
      /**
       * Registers a {@link org.springframework.web.bind.annotation.RestControllerAdvice} to catch all
       * exceptions thrown by the web layer. If there was no {@link WebErrorHandlers} in the application
       * context, then the advice would not be registered.
       *
       * @param webErrorHandlers           The exception handler.
       * @param httpErrorAttributesAdapter To adapt our and Spring Boot's error representations.
       * @return The registered controller advice.
       */
      @Bean
      @ConditionalOnBean(WebErrorHandlers.class)
      public ErrorsControllerAdvice errorsControllerAdvice(WebErrorHandlers webErrorHandlers,
                                                           HttpErrorAttributesAdapter httpErrorAttributesAdapter) {
          return new ErrorsControllerAdvice(webErrorHandlers, httpErrorAttributesAdapter) {
          };
      }
  
      /**
       * Registers a {@link ErrorAttributes} implementation which would replace the default one provided
       * by the Spring Boot. This {@link ErrorAttributes} would be used to adapt Spring Boot's error model
       * to our customized model.
       *
       * @param webErrorHandlers           To handle exceptions.
       * @param httpErrorAttributesAdapter Adapter between {@link me.alidg.errors.HttpError} and
       *                                   {@link ErrorAttributes}.
       * @return The to-be-registered {@link ServletErrorAttributes}.
       */
      @Bean
      @ConditionalOnBean(WebErrorHandlers.class)
      public ErrorAttributes errorAttributes(WebErrorHandlers webErrorHandlers,
                                             HttpErrorAttributesAdapter httpErrorAttributesAdapter) {
          return new ServletErrorAttributes(webErrorHandlers, httpErrorAttributesAdapter);
      }
  
      /**
       * Registers a custom {@link ErrorController} to change the default error handling approach.
       *
       * @param errorAttributes    Will be used to enrich error responses.
       * @param serverProperties   Will be used to access error related configurations.
       * @param errorViewResolvers All possible view resolvers to render the whitelabel error page.
       * @return The custom error controller instance.
       */
      @Bean
      @ConditionalOnBean(WebErrorHandlers.class)
      @ConditionalOnMissingBean(ErrorController.class)
      public BasicErrorController customErrorController(ErrorAttributes errorAttributes,
                                                   ServerProperties serverProperties,
                                                   ObjectProvider<ErrorViewResolver> errorViewResolvers) {
          List<ErrorViewResolver> resolvers = errorViewResolvers.orderedStream().collect(toList());
          return new CustomServletErrorController(errorAttributes, serverProperties.getError(), resolvers);
      }
  
      /**
       * A very simple custom {@link ErrorController} responsible for fixing the status code
       * issue in Spring Boot 2.2+.
       */
      private static class CustomServletErrorController extends BasicErrorController {
  
          public CustomServletErrorController(ErrorAttributes errorAttributes,
                                              ErrorProperties errorProperties,
                                              List<ErrorViewResolver> resolvers) {
              super(errorAttributes, errorProperties, resolvers);
          }
  
          /**
           * Since our custom {@link ErrorAttributes} implementation is storing the HTTP status code inside
           * the HTTP request, we should call the {@link #getStatus(HttpServletRequest)} method after the
           * call to {@link #getErrorAttributes(HttpServletRequest, boolean)}.
           *
           * @param request The current HTTP request.
           * @return Returns the HTTP response.
           */
          @Override
          @RequestMapping
          public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
              Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, ALL));
              HttpStatus status = getStatus(request);
              return new ResponseEntity<>(body, status);
          }
      }
  }
  