1 package de.juplo.thymeproxy;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.LinkedList;
11 import java.util.Properties;
12 import javax.servlet.ServletException;
13 import javax.servlet.http.HttpServletRequest;
14 import javax.servlet.http.HttpServletResponse;
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
17 import org.springframework.beans.factory.BeanFactoryUtils;
18 import org.springframework.beans.factory.BeanInitializationException;
19 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
20 import org.springframework.boot.autoconfigure.web.BasicErrorController;
21 import org.springframework.boot.autoconfigure.web.ErrorAttributes;
22 import org.springframework.boot.autoconfigure.web.ErrorProperties;
23 import org.springframework.context.ApplicationContext;
24 import org.springframework.core.annotation.AnnotationAwareOrderComparator;
25 import org.springframework.core.io.ClassPathResource;
26 import org.springframework.core.io.support.PropertiesLoaderUtils;
27 import org.springframework.http.HttpStatus;
28 import org.springframework.http.MediaType;
29 import org.springframework.util.ClassUtils;
30 import org.springframework.util.StringUtils;
31 import org.springframework.web.servlet.DispatcherServlet;
32 import static org.springframework.web.servlet.DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME;
33 import org.springframework.web.servlet.HandlerExceptionResolver;
34 import org.springframework.web.servlet.ModelAndView;
42 public class ExceptionResolverErrorController extends BasicErrorController
44 private final static Logger LOG =
45 LoggerFactory.getLogger(ExceptionResolverErrorController.class);
47 public final static String EXCEPTION_ATTRIBUTE =
48 "javax.servlet.error.exception";
52 * Name of the class path resource (relative to the DispatcherServlet class)
53 * that defines DispatcherServlet's default strategy names.
55 private static final String DEFAULT_STRATEGIES_PATH =
56 "DispatcherServlet.properties";
58 private static final Properties DEFAULT_STRATEGIES;
61 // Load default strategy implementations from properties file.
62 // This is currently strictly internal and not meant to be customized
63 // by application developers.
66 ClassPathResource resource =
67 new ClassPathResource(
68 DEFAULT_STRATEGIES_PATH,
69 DispatcherServlet.class
71 DEFAULT_STRATEGIES = PropertiesLoaderUtils.loadProperties(resource);
73 catch (IOException ex)
75 throw new IllegalStateException(
76 "Could not load 'DispatcherServlet.properties': " +
83 /** Name of the default-error-view */
84 private String defaultErrorView = "error";
86 /** Mapping from HTTP-status-codes to specialized error-pages */
87 private Map<HttpStatus, String> errorMappings = new HashMap<>();
89 /** List of HandlerExceptionResolvers used by this servlet */
90 private List<HandlerExceptionResolver> handlerExceptionResolvers;
92 /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
93 private boolean detectAllHandlerExceptionResolvers = true;
96 ExceptionResolverErrorController(
97 ApplicationContext context,
98 ErrorAttributes errorAttributes,
99 ErrorProperties errorProperties
102 super(errorAttributes, errorProperties);
103 initHandlerExceptionResolvers(context);
108 public ModelAndView errorHtml(
109 HttpServletRequest request,
110 HttpServletResponse response
113 Map<String, Object> model =
116 isIncludeStackTrace(request, MediaType.TEXT_HTML)
119 ModelAndView view = null;
121 Exception e = (Exception)request.getAttribute(EXCEPTION_ATTRIBUTE);
124 if (e instanceof ServletException )
126 ServletException n = (ServletException)e;
127 Throwable t = n.getRootCause();
128 if (t != null && t instanceof Exception)
132 for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers)
134 view = resolver.resolveException(request, response, null, e);
141 view.addAllObjects(model);
146 String viewName = null;
147 Integer code = (Integer)model.get("status");
150 HttpStatus status = HttpStatus.valueOf(code);
151 viewName = errorMappings.get(status);
155 LOG.warn("cannot map status-code {}: {}", code, t.getMessage());
157 if (viewName == null)
158 viewName = defaultErrorView;
159 return new ModelAndView(viewName, model);
164 * Initialize the HandlerExceptionResolver used by this class.
166 * If no bean is defined with the given name in the BeanFactory for this
168 * we default to no exception resolver.
170 private void initHandlerExceptionResolvers(ApplicationContext context)
172 this.handlerExceptionResolvers = null;
174 if (this.detectAllHandlerExceptionResolvers)
176 // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
177 Map<String, HandlerExceptionResolver> matchingBeans =
178 BeanFactoryUtils.beansOfTypeIncludingAncestors(
180 HandlerExceptionResolver.class,
184 if (!matchingBeans.isEmpty())
186 this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
187 // We keep HandlerExceptionResolvers in sorted order.
188 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
195 HandlerExceptionResolver her =
197 HANDLER_EXCEPTION_RESOLVER_BEAN_NAME,
198 HandlerExceptionResolver.class
200 this.handlerExceptionResolvers = Collections.singletonList(her);
202 catch (NoSuchBeanDefinitionException e)
204 // Ignore, no HandlerExceptionResolver is fine too.
208 // Ensure we have at least some HandlerExceptionResolvers, by registering
209 // default HandlerExceptionResolvers if no other resolvers are found.
210 if (this.handlerExceptionResolvers == null)
212 this.handlerExceptionResolvers =
213 getDefaultStrategies(context, HandlerExceptionResolver.class);
214 LOG.debug("No HandlerExceptionResolvers found: using default");
219 * Create a List of default strategy objects for the given strategy interface.
221 * The default implementation uses the "DispatcherServlet.properties" file (in
223 * package as the DispatcherServlet class) to determine the class names. It
225 * the strategy objects through the context's BeanFactory.
227 * @param context the current WebApplicationContext
228 * @param strategyInterface the strategy interface
229 * @return the List of corresponding strategy objects
231 @SuppressWarnings("unchecked")
232 protected <T> List<T> getDefaultStrategies(
233 ApplicationContext context,
234 Class<T> strategyInterface
237 String key = strategyInterface.getName();
238 String value = DEFAULT_STRATEGIES.getProperty(key);
241 String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
242 List<T> strategies = new ArrayList<>(classNames.length);
243 for (String className : classNames)
250 DispatcherServlet.class.getClassLoader()
252 Object strategy = createDefaultStrategy(context, clazz);
253 strategies.add((T) strategy);
255 catch (ClassNotFoundException ex)
257 throw new BeanInitializationException(
258 "Could not find DispatcherServlet's default strategy class [" +
259 className + "] for interface [" + key + "]",
263 catch (LinkageError err)
265 throw new BeanInitializationException(
266 "Error loading DispatcherServlet's default strategy class [" +
267 className + "] for interface [" + key +
268 "]: problem with class file or dependent class",
277 return new LinkedList<>();
282 * Create a default strategy.
284 * The default implementation uses
285 * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
287 * @param context the current WebApplicationContext
288 * @param clazz the strategy implementation class to instantiate
289 * @return the fully configured strategy instance
291 * org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
293 * org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean
295 protected Object createDefaultStrategy(
296 ApplicationContext context,
300 return context.getAutowireCapableBeanFactory().createBean(clazz);
306 * @see #addErrorMapping(HttpStatus, String)
308 public String addErrorMapping(Integer status, String viewName)
311 throw new IllegalArgumentException("The status must not be null");
312 return addErrorMapping(HttpStatus.valueOf(status), viewName);
316 * Adds a mapping from a {@link HttpStatus} to a view.
318 * @param status The {@link HttpStatus}, that should be mapped.
319 * @param viewName The name of the view, the status should be mapped to.
320 * @return The name of the view, the status was previously mapped to, or
321 * <code>null</code>, if the status was not mapped before.
323 public String addErrorMapping(HttpStatus status, String viewName)
325 if (!StringUtils.hasText(viewName))
326 throw new IllegalArgumentException("The view-name must not be empty!");
328 throw new IllegalArgumentException("The status must not be null!");
329 return errorMappings.put(status, viewName);
333 * Sets mappings from {@link HttpStatus} to specialized error-views.
334 * @param mappings The mappings to set.
336 public void setErrorMappings(Map<HttpStatus, String> mappings)
338 errorMappings = mappings;
342 * Sets the default error-view for not-mapped status-codes.
343 * @param view The default error-view to set.
345 public void setDefaultErrorView(String view)
347 defaultErrorView = view;
351 * Set whether to detect all HandlerExceptionResolver beans in this servlet's
352 * context. Otherwise,
353 * just a single bean with name "handlerExceptionResolver" will be expected.
355 * Default is "true". Turn this off if you want this servlet to use a single
356 * HandlerExceptionResolver, despite multiple HandlerExceptionResolver beans
357 * being defined in the context.
359 public void setDetectAllHandlerExceptionResolvers(boolean resolvers)
361 detectAllHandlerExceptionResolvers = resolvers;