import de.juplo.thymeleaf.JuploDialect;
import java.util.HashMap;
-import java.util.Locale;
import java.util.Map;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.apache.http.HttpStatus;
+import java.util.Properties;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.autoconfigure.web.BasicErrorController;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
-import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
-import org.springframework.http.MediaType;
-import org.springframework.web.servlet.ModelAndView;
-import org.springframework.web.servlet.View;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.mvc.UrlFilenameViewController;
-import org.thymeleaf.exceptions.TemplateInputException;
import org.thymeleaf.resourceresolver.IResourceResolver;
import org.thymeleaf.resourceresolver.UrlResourceResolver;
-import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
}
@Bean
- public View error(ThymeleafViewResolver resolver) throws Exception
+ public SimpleMappingExceptionResolver simpleMappingExceptionResolver()
{
- return resolver.resolveViewName("templates/500", Locale.getDefault());
+ SimpleMappingExceptionResolver resolver =
+ new SimpleMappingExceptionResolver();
+
+ Properties mappings = new Properties();
+ mappings.setProperty("TemplateInputException", "templates/404");
+
+ resolver.setExceptionMappings(mappings);
+ resolver.setDefaultErrorView("templates/500");
+ resolver.setWarnLogCategory("exception");
+ return resolver;
}
@Bean
- public ErrorController errorController(ErrorAttributes errorAttributes)
+ public ErrorController errorController(
+ ApplicationContext context,
+ ErrorAttributes errorAttributes
+ )
{
- return new CustomErrorController(errorAttributes, properties.getError());
+ return new ExceptionResolverErrorController(
+ context,
+ errorAttributes,
+ properties.getError()
+ );
}
{
SpringApplication.run(Application.class, args);
}
-
-
- static class CustomErrorController extends BasicErrorController
- {
- public final static String TEMPLATE_INPUT_EXCEPTION =
- TemplateInputException.class.getCanonicalName();
-
-
- CustomErrorController(
- ErrorAttributes errorAttributes,
- ErrorProperties errorProperties
- )
- {
- super(errorAttributes, errorProperties);
- }
-
-
- @Override
- public ModelAndView errorHtml(
- HttpServletRequest request,
- HttpServletResponse response
- )
- {
- Map<String, Object> model =
- getErrorAttributes(
- request,
- isIncludeStackTrace(request, MediaType.TEXT_HTML)
- );
-
- String view;
-
- switch ((String)model.get("exception"))
- {
- case "org.thymeleaf.exceptions.TemplateInputException":
- response.setStatus(HttpStatus.SC_NOT_FOUND);
- view = "templates/404";
- break;
-
- default:
- response.setStatus(getStatus(request).value());
- view = "templates/500";
- break;
- }
-
- return new ModelAndView(view, model);
- }
- }
}
\ No newline at end of file
--- /dev/null
+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;
+ }
+}