content: package me.alidg.errors.conf;
  
  import me.alidg.errors.*;
  import me.alidg.errors.adapter.DefaultHttpErrorAttributesAdapter;
  import me.alidg.errors.adapter.HttpErrorAttributesAdapter;
  import me.alidg.errors.fingerprint.UuidFingerprintProvider;
  import me.alidg.errors.handlers.*;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Qualifier;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
  import org.springframework.boot.context.properties.EnableConfigurationProperties;
  import org.springframework.boot.web.servlet.error.ErrorAttributes;
  import org.springframework.context.ApplicationContext;
  import org.springframework.context.MessageSource;
  import org.springframework.context.annotation.Bean;
  import org.springframework.core.annotation.AnnotationAwareOrderComparator;
  import org.springframework.web.context.WebApplicationContext;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Objects;
  
  import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;
  
  /**
   * Auto-configuration responsible for registering a {@link WebErrorHandlers} filled with
   * builtin, custom and default fallback {@link WebErrorHandler}s.
   *
   * <h3>Builtin Web Error Handlers</h3>
   * Built in {@link WebErrorHandler}s are those we provided out of the box. It's highly recommended
   * to use these implementations with most possible priority, as we did in this auto-configuration.
   *
   * <h3>Custom Web Error Handlers</h3>
   * You can also provide your own custom {@link WebErrorHandler} implementations. Just implement the
   * {@link WebErrorHandler} interface and register it as Spring Bean. If you're willing to prioritize
   * your implementations, use the Spring {@link org.springframework.core.annotation.Order} annotation
   * to specify the priority requirements of the bean.
   * <p><b>Please Note that</b> your custom handlers would be registered after the built-in ones. If you're
   * not OK with that, you can always discard this auto-configuration by registering your own
   * {@link WebErrorHandlers} factory bean.</p>
   *
   * <h3>Default Fallback Web Error Handler</h3>
   * While handling a particular exception, each registered {@link WebErrorHandler} in {@link WebErrorHandlers}
   * would be consulted one after another (Depending on their priority). If all of the registered handlers
   * refuse to handle the exception, then a default fallback {@link WebErrorHandler} should handle the exception.
   * By default, {@link WebErrorHandlers} use the {@link me.alidg.errors.handlers.LastResortWebErrorHandler} as the
   * default handler. You can replace this handler by providing a {@link WebErrorHandler} and register it with
   * a bean named {@code defaultWebErrorHandler}.
   *
   * @author Ali Dehghani
   * @see WebErrorHandler
   * @see WebErrorHandlers
   * @see me.alidg.errors.handlers.LastResortWebErrorHandler
   */
  @ConditionalOnWebApplication
  @EnableConfigurationProperties(ErrorsProperties.class)
  public class ErrorsAutoConfiguration {
  
      /**
       * Built-in {@link WebErrorHandler}s which would be on top of all other {@link WebErrorHandler}s
       * and will be consulted before any other implementations for error handling.
       */
      private static final List<WebErrorHandler> BUILT_IN_HANDLERS = Arrays.asList(
          new SpringValidationWebErrorHandler(),
          new ConstraintViolationWebErrorHandler(),
          new AnnotatedWebErrorHandler(),
          new TypeMismatchWebErrorHandler(),
          new MultipartWebErrorHandler()
      );
  
      /**
       * Registers a bean of type {@link WebErrorHandlers} (If not provided by the user) filled with a set of
       * built-in {@link WebErrorHandler}s, a set of custom {@link WebErrorHandler}s and a default fallback
       * {@link WebErrorHandler}.
       *
       * @param messageSource                 Will be used for error code to error message translation.
       * @param customHandlers                Optional custom {@link WebErrorHandler}s.
       * @param defaultWebErrorHandler        A default {@link WebErrorHandler} to be used as the fallback error handler.
       * @param exceptionRefiner              To refine exceptions before handling them.
       * @param exceptionLogger               To log exceptions.
       * @param webErrorHandlerPostProcessors Post processors to execute after we handled the exception.
       * @param fingerprintProvider           To generate unique fingerprints for handled exceptions.
       * @param errorsProperties              Configuration properties bean.
       * @param context                       To tell Servlet or Reactive stacks apart.
       * @return The expected {@link WebErrorHandlers}.
       */
      @Bean
      @ConditionalOnMissingBean
      public WebErrorHandlers webErrorHandlers(MessageSource messageSource,
                                               @Autowired(required = false) List<WebErrorHandler> customHandlers,
                                               @Qualifier("defaultWebErrorHandler") @Autowired(required = false) WebErrorHandler defaultWebErrorHandler,
                                               ExceptionRefiner exceptionRefiner,
                                               ExceptionLogger exceptionLogger,
                                               @Autowired(required = false) List<WebErrorHandlerPostProcessor> webErrorHandlerPostProcessors,
                                               FingerprintProvider fingerprintProvider,
                                               ErrorsProperties errorsProperties,
                                               ApplicationContext context) {
  
          List<WebErrorHandler> handlers = new ArrayList<>(BUILT_IN_HANDLERS);
          if (isServletApplication(context)) handlers.add(new ServletWebErrorHandler());
  
          if (customHandlers != null && !customHandlers.isEmpty()) {
              customHandlers.remove(defaultWebErrorHandler);
              customHandlers.removeIf(Objects::isNull);
              customHandlers.sort(AnnotationAwareOrderComparator.INSTANCE);
  
              handlers.addAll(customHandlers);
          }
  
          WebErrorHandlersBuilder builder = WebErrorHandlers
              .builder(messageSource)
              .withErrorsProperties(errorsProperties)
              .withErrorHandlers(handlers)
              .withExceptionRefiner(exceptionRefiner)
              .withExceptionLogger(exceptionLogger)
              .withFingerprintProvider(fingerprintProvider);
  
          if (defaultWebErrorHandler != null) builder.withDefaultWebErrorHandler(defaultWebErrorHandler);
          if (webErrorHandlerPostProcessors != null) builder.withPostProcessors(webErrorHandlerPostProcessors);
  
          return builder.build();
      }
  
      /**
       * In the absence of a bean of type {@link HttpErrorAttributesAdapter}, registers the default
       * implementation of {@link HttpErrorAttributesAdapter} as a bean, to adapt our
       * {@link me.alidg.errors.HttpError} to Spring's {@link ErrorAttributes} abstraction.
       *
       * @param errorsProperties Configuration properties bean.
       * @return The to-be-registered {@link HttpErrorAttributesAdapter}.
       */
      @Bean
      @ConditionalOnBean(WebErrorHandlers.class)
      @ConditionalOnMissingBean(HttpErrorAttributesAdapter.class)
      public HttpErrorAttributesAdapter httpErrorAttributesAdapter(ErrorsProperties errorsProperties) {
          return new DefaultHttpErrorAttributesAdapter(errorsProperties);
      }
  
      /**
       * Registers a {@link WebErrorHandler} bean to handle Spring Security specific exceptions when
       * Spring Security's jar file is present on the classpath.
       *
       * @return A web error handler for Spring Security exceptions.
       */
      @Bean
      @ConditionalOnBean(WebErrorHandlers.class)
      @ConditionalOnClass(name = "org.springframework.security.access.AccessDeniedException")
      public SpringSecurityWebErrorHandler springSecurityWebErrorHandler() {
          return new SpringSecurityWebErrorHandler();
      }
  
      /**
       * Registers a {@link WebErrorHandler} to handle new Servlet exceptions defined in Spring Framework 5.1.
       * This handler would be registered iff we're using Spring Boot 2.1.0+.
       *
       * @return A web error handler to handle a few new servlet exceptions.
       */
      @Bean
      @ConditionalOnBean(WebErrorHandlers.class)
      @ConditionalOnWebApplication(type = SERVLET)
      @ConditionalOnClass(name = "org.springframework.web.bind.MissingRequestHeaderException")
      public MissingRequestParametersWebErrorHandler missingRequestParametersWebErrorHandler() {
          return new MissingRequestParametersWebErrorHandler();
      }
  
      /**
       * Registers a handler expert at handling all possible
       * {@link org.springframework.web.server.ResponseStatusException}s.
       *
       * @return A web error handler to handle a set of brand new exceptions defined in Spring 5.x.
       */
      @Bean
      @ConditionalOnBean(WebErrorHandlers.class)
      @ConditionalOnClass(name = "org.springframework.web.server.ResponseStatusException")
      public ResponseStatusWebErrorHandler responseStatusWebErrorHandler() {
          return new ResponseStatusWebErrorHandler();
      }
  
      /**
       * Registers an empty {@link ExceptionRefiner} in the absence of a custom refiner.
       *
       * @return A no-op exception refiner.
       */
      @Bean
      @ConditionalOnMissingBean
      @ConditionalOnBean(WebErrorHandlers.class)
      public ExceptionRefiner exceptionRefiner() {
          return ExceptionRefiner.NoOp.INSTANCE;
      }
  
      /**
       * Registers an empty {@link ExceptionLogger} in the absence of a custom logger.
       *
       * @return A no-op exception logger.
       */
      @Bean
      @ConditionalOnMissingBean
      @ConditionalOnBean(WebErrorHandlers.class)
      public ExceptionLogger exceptionLogger() {
          return ExceptionLogger.NoOp.INSTANCE;
      }
  
      /**
       * Registers a very simple UUID based {@link FingerprintProvider} in the absence of a custom provider.
       *
       * @return The UUID based fingerprint provider.
       */
      @Bean
      @ConditionalOnMissingBean
      @ConditionalOnBean(WebErrorHandlers.class)
      public FingerprintProvider fingerprintProvider() {
          return new UuidFingerprintProvider();
      }
  
      /**
       * @param context The application context.
       * @return {@code true} if this a traditional web application, not a reactive one.
       */
      private boolean isServletApplication(ApplicationContext context) {
          return context instanceof WebApplicationContext;
      }
  }
  