+package de.juplo.thymeproxy;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.boot.autoconfigure.web.BasicErrorController;
+import org.springframework.boot.autoconfigure.web.ErrorAttributes;
+import org.springframework.boot.autoconfigure.web.ErrorProperties;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.http.MediaType;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.DispatcherServlet;
+import static org.springframework.web.servlet.DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.ModelAndView;
+
+
+
+/**
+ *
+ * @author kai
+ */
+public class ExceptionResolverErrorController extends BasicErrorController
+{
+ private final static Logger LOG =
+ LoggerFactory.getLogger(ExceptionResolverErrorController.class);
+
+ public final static String EXCEPTION_ATTRIBUTE =
+ "javax.servlet.error.exception";
+
+
+ /**
+ * Name of the class path resource (relative to the DispatcherServlet class)
+ * that defines DispatcherServlet's default strategy names.
+ */
+ private static final String DEFAULT_STRATEGIES_PATH =
+ "DispatcherServlet.properties";
+ private static final Properties DEFAULT_STRATEGIES;
+
+ static
+ {
+ // Load default strategy implementations from properties file.
+ // This is currently strictly internal and not meant to be customized
+ // by application developers.
+ try
+ {
+ ClassPathResource resource =
+ new ClassPathResource(
+ DEFAULT_STRATEGIES_PATH,
+ DispatcherServlet.class
+ );
+ DEFAULT_STRATEGIES = PropertiesLoaderUtils.loadProperties(resource);
+ }
+ catch (IOException ex)
+ {
+ throw new IllegalStateException(
+ "Could not load 'DispatcherServlet.properties': " +
+ ex.getMessage()
+ );
+ }
+ }
+
+
+ /** List of HandlerExceptionResolvers used by this servlet */
+ private List<HandlerExceptionResolver> handlerExceptionResolvers;
+
+ /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
+ private boolean detectAllHandlerExceptionResolvers = true;
+
+
+ ExceptionResolverErrorController(
+ ApplicationContext context,
+ ErrorAttributes errorAttributes,
+ ErrorProperties errorProperties
+ )
+ {
+ super(errorAttributes, errorProperties);
+ initHandlerExceptionResolvers(context);
+ }
+
+
+ @Override
+ public ModelAndView errorHtml(
+ HttpServletRequest request,
+ HttpServletResponse response
+ )
+ {
+ Map<String, Object> model =
+ getErrorAttributes(
+ request,
+ isIncludeStackTrace(request, MediaType.TEXT_HTML)
+ );
+
+ ModelAndView view = null;
+
+ Exception e = (Exception)request.getAttribute(EXCEPTION_ATTRIBUTE);
+ if (e != null)
+ {
+ if (e instanceof ServletException )
+ {
+ ServletException n = (ServletException)e;
+ Throwable t = n.getRootCause();
+ if (t != null && t instanceof Exception)
+ e = (Exception)t;
+ }
+
+ for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers)
+ {
+ view = resolver.resolveException(request, response, null, e);
+ if (view != null)
+ break;
+ }
+
+ if (view != null)
+ {
+ view.addAllObjects(model);
+ return view;
+ }
+ }
+
+ return new ModelAndView("error", model);
+ }
+
+
+ /**
+ * Initialize the HandlerExceptionResolver used by this class.
+ * <p>
+ * If no bean is defined with the given name in the BeanFactory for this
+ * namespace,
+ * we default to no exception resolver.
+ */
+ private void initHandlerExceptionResolvers(ApplicationContext context)
+ {
+ this.handlerExceptionResolvers = null;
+
+ if (this.detectAllHandlerExceptionResolvers)
+ {
+ // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
+ Map<String, HandlerExceptionResolver> matchingBeans =
+ BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ context,
+ HandlerExceptionResolver.class,
+ true,
+ false
+ );
+ if (!matchingBeans.isEmpty())
+ {
+ this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
+ // We keep HandlerExceptionResolvers in sorted order.
+ AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
+ }
+ }
+ else
+ {
+ try
+ {
+ HandlerExceptionResolver her =
+ context.getBean(
+ HANDLER_EXCEPTION_RESOLVER_BEAN_NAME,
+ HandlerExceptionResolver.class
+ );
+ this.handlerExceptionResolvers = Collections.singletonList(her);
+ }
+ catch (NoSuchBeanDefinitionException e)
+ {
+ // Ignore, no HandlerExceptionResolver is fine too.
+ }
+ }
+
+ // Ensure we have at least some HandlerExceptionResolvers, by registering
+ // default HandlerExceptionResolvers if no other resolvers are found.
+ if (this.handlerExceptionResolvers == null)
+ {
+ this.handlerExceptionResolvers =
+ getDefaultStrategies(context, HandlerExceptionResolver.class);
+ LOG.debug("No HandlerExceptionResolvers found: using default");
+ }
+ }
+
+ /**
+ * Create a List of default strategy objects for the given strategy interface.
+ * <p>
+ * The default implementation uses the "DispatcherServlet.properties" file (in
+ * the same
+ * package as the DispatcherServlet class) to determine the class names. It
+ * instantiates
+ * the strategy objects through the context's BeanFactory.
+ *
+ * @param context the current WebApplicationContext
+ * @param strategyInterface the strategy interface
+ * @return the List of corresponding strategy objects
+ */
+ @SuppressWarnings("unchecked")
+ protected <T> List<T> getDefaultStrategies(
+ ApplicationContext context,
+ Class<T> strategyInterface
+ )
+ {
+ String key = strategyInterface.getName();
+ String value = DEFAULT_STRATEGIES.getProperty(key);
+ if (value != null)
+ {
+ String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
+ List<T> strategies = new ArrayList<>(classNames.length);
+ for (String className : classNames)
+ {
+ try
+ {
+ Class<?> clazz =
+ ClassUtils.forName(
+ className,
+ DispatcherServlet.class.getClassLoader()
+ );
+ Object strategy = createDefaultStrategy(context, clazz);
+ strategies.add((T) strategy);
+ }
+ catch (ClassNotFoundException ex)
+ {
+ throw new BeanInitializationException(
+ "Could not find DispatcherServlet's default strategy class [" +
+ className + "] for interface [" + key + "]",
+ ex
+ );
+ }
+ catch (LinkageError err)
+ {
+ throw new BeanInitializationException(
+ "Error loading DispatcherServlet's default strategy class [" +
+ className + "] for interface [" + key +
+ "]: problem with class file or dependent class",
+ err
+ );
+ }
+ }
+ return strategies;
+ }
+ else
+ {
+ return new LinkedList<>();
+ }
+ }
+
+ /**
+ * Create a default strategy.
+ * <p>
+ * The default implementation uses
+ * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
+ *
+ * @param context the current WebApplicationContext
+ * @param clazz the strategy implementation class to instantiate
+ * @return the fully configured strategy instance
+ * @see
+ * org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
+ * @see
+ * org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean
+ */
+ protected Object createDefaultStrategy(
+ ApplicationContext context,
+ Class<?> clazz
+ )
+ {
+ return context.getAutowireCapableBeanFactory().createBean(clazz);
+ }
+
+
+ /**
+ * Set whether to detect all HandlerExceptionResolver beans in this servlet's
+ * context. Otherwise,
+ * just a single bean with name "handlerExceptionResolver" will be expected.
+ * <p>
+ * Default is "true". Turn this off if you want this servlet to use a single
+ * HandlerExceptionResolver, despite multiple HandlerExceptionResolver beans
+ * being defined in the context.
+ */
+ public void setDetectAllHandlerExceptionResolvers(boolean resolvers)
+ {
+ detectAllHandlerExceptionResolvers = resolvers;
+ }
+}