How to globally handle 404 exception by returning a customized error page in Spring MVC? - spring

I need to return a customized error page for HTTP 404, but the code does not work. I already read the stackflow 1,2, 3 and different online + articles but my case is quite different with those or at least I can not figure out the issue.
HTTP Status 404 -
type Status report
message
description The requested resource is not available.
For example, it is suggested to use an action name in web.xml to handle the exception, it might work but I think is not a good method of doing it.
I used following combinations:
1)
#Controller //for class
#ResponseStatus(value = HttpStatus.NOT_FOUND) //for method
2)
#Controller //for class
#ResponseStatus(value = HttpStatus.NOT_FOUND) //for method
#ExceptionHandler(ResourceNotFoundException.class) //for method
3)
#ControllerAdvice //for class
#ResponseStatus(value = HttpStatus.NOT_FOUND) //for method
3)
#ControllerAdvice //for class
#ResponseStatus(HttpStatus.NOT_FOUND) //for method
4)
#ControllerAdvice //for class
#ResponseStatus(value = HttpStatus.NOT_FOUND) //for method
#ExceptionHandler(ResourceNotFoundException.class) //for method
Code
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public String handleBadRequest(Exception exception) {
return "error404";
}
}

DispatcherServlet does not throw an exception by default, if it does not find the handler to process the request. So you need to explicitly activate it as below:
In web.xml:
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
If you are using annotation based configuration:
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
In the controller advice class:
#ExceptionHandler
#ResponseStatus(HttpStatus.NOT_FOUND)
public String handleExceptiond(NoHandlerFoundException ex) {
return "errorPage";
}

You can use the #ResponseStatus annotation at the class level. At least it works for me.
#Controller
#ResponseStatus(value=HttpStatus.NOT_FOUND)
public class GlobalExceptionHandler {
....
}

You can add the following in your web.xml to display an error page on 404. It will display 404.jsp page in views folder inside WEB-INF folder.
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/404.jsp</location>
</error-page>

Related

How to configure Spring errror page in JSON format with web.xml and an error controller

With Spring 5, I am currently able to configure an error page in src/main/webapp/web.xml, i.e. the following configuration is added:
<error-page>
<location>/WEB-INF/error.html</location>
</error-page>
In this way, the error.html will be rendered when there is Exception in the controller. However, this error.html is in html format other than the expected JSON format.
I tried to make an error controller with some code like this:
#RestController
#RequestMapping(value = "/handler")
public class ErrorController {
#RequestMapping(value = "/errors")
public String renderErrorPage(HttpServletRequest httpRequest) {
System.out.println("DEBUG::come to error page");
return "test error";
}
}
In the same time configured error-page as such:
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<error-page>
<location>/handler/errors</location>
</error-page>
But the ErrorController cannot be invoked.
Question: How to configure Spring error page in JSON format with web.xml and an error controller?
I eventually realized that the servlats are filtered with /rest/* through the servlet-mapping tag, so the location of the error-page has to be prefixed with /rest, meaning the error-page tag should be configured as such:
<error-page>
<location>/rest/errors</location>
</error-page>
Correspondingly the controller can be configured like:
#RestController
//#RequestMapping(value = "/handler")
public class ErrorController {
#RequestMapping(value = "/errors")
public String renderErrorPage(HttpServletRequest httpRequest) {
System.out.println("DEBUG::come to error page");
return "test error";
}
}

How to allow GET method for endpoint programmatically?

I am loading a .war file and add it as web app to the embedded Tomcat server.
#Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
LOGGER.info("Adding web app");
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
String appHome = System.getProperty(Environment.APP_HOME);
String targetFileName = "web-0.0.1-SNAPSHOT.war";
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(targetFileName);
LOGGER.info(System.getProperty("user.name"));
LOGGER.debug("Loading WAR from " + appHome);
File target = new File(Paths.get(appHome, targetFileName).toString());
try {
LOGGER.info(String.format("Copy %s to %s", targetFileName, target.getAbsoluteFile().toPath()));
java.nio.file.Files.copy(resourceAsStream, target.getAbsoluteFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
Context context = tomcat.addWebapp("/", target.getAbsolutePath());
context.setParentClassLoader(getClass().getClassLoader());
} catch (ServletException ex) {
throw new IllegalStateException("Failed to add webapp.", ex);
} catch (Exception e) {
throw new IllegalStateException("Unknown error while trying to load webapp.", e);
}
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
This is working so far but if I access http://localhost:8080/web I am getting
2017-03-04 11:18:59.588 WARN 29234 --- [nio-8080-exec-2] o.s.web.servlet.PageNotFound : Request method 'GET' not supported
and the response
Allow: POST
Content-Length: 0
Date: Sat, 04 Mar 2017 10:26:16 GMT
I am sure all I have to do is to allow the GET method on /web and hopefully the static web content provided from the loaded war file will be accessible via web browser.
How/where can I configure the endpoint such that it allows GET requests?
I tried to introduce a WebController as described in this tutorial.
#Controller
public class WebController {
private final static Logger LOGGER = Logger.getLogger(WebController.class);
#RequestMapping(value = "/web", method = RequestMethod.GET)
public String index() {
LOGGER.info("INDEX !");
return "index";
}
}
In the log output I can see that this is getting mapped correctly:
RequestMappingHandlerMapping : Mapped "{[/web],methods=[GET]}" onto public java.lang.String org.ema.server.spring.controller.dl4j.WebController.index()
but it does not change the fact that I cannot visit the website.
I've also configured a InternalResourceViewResolver:
#Configuration
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter {
private final static Logger LOGGER = Logger.getLogger(MvcConfiguration.class);
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
LOGGER.info("configureViewResolvers()");
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setSuffix(".html");
registry.viewResolver(resolver);
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
web.xml
Since I configure everything in pure Java, this file does not define a lot:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Easy Model Access Server</display-name>
<listener>
<listener-class>org.ema.server.ServerEntryPoint</listener-class>
</listener>
<context-param>
<param-name>log4j-config-location</param-name>
<param-value>WEB-INF/classes/log4j.properties</param-value>
</context-param>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/web/*.html</url-pattern>
</servlet-mapping>
</web-app>
Reproduce
If you want to reproduce this you can simply checkout the entire code from github. All you need to do this:
mkdir ~/.ema
git clone https://github.com/silentsnooc/easy-model-access
cd easy-model-access/ema-server
mvn clean install
java -jar server/target/server-*.jar
This will clone, build and run the server.
The directory ~/.ema directory is required at the moment. It is where the WAR is being copied as the server starts.
My guess is that your web.xml maps any path to the Spring DispatcherServlet, something like:
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Because of <url-pattern>/</url-pattern> any request must be handled by a Spring controller, for this reason your static files are not served by Tomcat. Also a pattern like /*.html would have same effect.
If you have only a few pages you might add one or more mapping to the predefined default servlet for them, before the mapping of Spring (and also before Spring Security if you use it):
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>index.html</url-pattern>
</servlet-mapping>
You may also use <url-pattern>*.html</url-pattern> or, if your resources are under the web path and there are only static resources there: <url-pattern>/web/*</url-pattern>
Maybe all this is done instead in Java code in the org.ema.server.ServerEntryPoint that you have as a listener in web.xml
I think the mapping I wrote up in web.xml is done in your case in method getServletMappings of class org.ema.server.spring.config.AppInitializer, I changed it to use a more strict pattern /rest-api/* instead than /, not sure pattern is correct and everything else works, but now http://127.0.0.1:8080/index.html works
#Override
protected String[] getServletMappings() {
return new String[] { "/rest-api/*" };
}
as I see the url: http://localhost:8080/web is wrong.
You can try: http://localhost:8080/[name-of-war-file]/web

Spring Data Rest: ResourceProcessor configuration is not working properly

I have a strange behaviour with a Spring Data Rest implementation (version 2.5.2.RELEASE).
I'm trying to register a #Bean of ResourceProcessor<Resource<Entity>>, but there is something strange.
I'm trying with two kinds of solutions:
1) Declaring the #Bean in a class:
#Bean
public ResourceProcessor<Resource<Author>> authorProcessor() {
return new ResourceProcessor<Resource<Author>>() {
#Override
public Resource<Author> process(Resource<Author> resource) {
System.out.println("method process of bean ResourceProcessor of class RepositoryBaseConfiguration");
return resource;
}
};
}
2) Implementing the interface ResourceProcessor:
#Component
public class AuthorResourceProcessor implements ResourceProcessor<Resource<Author>> {
#Override
public Resource<Author> process(Resource<Author> resource) {
System.out.println("method process of class AuthorResourceProcessor");
return resource;
}
}
The processors are completely ignored: the message is never printed.
I noticed that the class org.springframework.data.rest.webmvc.ResourceProcessorInvoker has a constructor:
public ResourceProcessorInvoker(Collection<ResourceProcessor<?>> processors) {
//...
}
This constructor is invoked 2 times at the start of the application instead of only one time (as I will expect), and I don't understand why.
The first time, the "processors" variable is solved with the two beans (as expected) and with the bean org.springframework.data.rest.webmvc.ProfileResourceProcessor.
But the second time, the "processors" variable is solved with only the bean org.springframework.data.rest.webmvc.ProfileResourceProcessor.
The second configuration #Override the first one.
Any idea?
The problem depends on the configurations loaded at the startup of the application.
I had this configuration on the web.xml:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/spring-web-config.xml</param-value>
</context-param>
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>org.springframework.data.rest.webmvc.RepositoryRestDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
So, the ContextLoaderListener loaded the correct configuration in the first time; the "load-on-startup" property of the servlet "RepositoryRestDispatcherServlet" launch a second context configuration load.
I also had a custom class that extended org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration, but this custom class was ignored by the moment that the constructor of RepositoryRestDispatcherServlet load the default RepositoryRestMvcConfiguration, causing the lost of the configurations.
To solve that issue I have created a custom RepositoryRestDispatcherServlet in this way:
public class AppRepositoryRestDispatcherServlet extends DispatcherServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public AppRepositoryRestDispatcherServlet() {
configure();
}
public AppRepositoryRestDispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
configure();
}
private void configure() {
setContextClass(AnnotationConfigWebApplicationContext.class);
setContextConfigLocation(RepositoryBaseConfiguration.class.getName());
}
}
The class is the same as RepositoryRestDispatcherServlet, with the only difference that in the setContextConfigLocation is passed the custom class that extends RepositoryRestMvcConfiguration (RepositoryBaseConfiguration in this example).
Obviously I had to update the web.xml as follows:
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>my.package.AppRepositoryRestDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
In this way, the configuration is correctly loaded and mantained.

Having issue with mapping in spring dispatcher servlet

I have a JSP page that contains a form and on submitting that form I need to call GET method. But with my current mapping, when I try to load my JSP page then instead of loading the JSP page it calls GET method.
Following are the mappings that I made.
web.xml:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping>
TestHarnessController :
#Controller
#RequestMapping("/testHarness")
public class TestHarnessController {
#RequestMapping(method = RequestMethod.GET)
public String handleGetRequest(HttpServletRequest request, HttpServletResponse response) {
return null;
}
#RequestMapping(method = RequestMethod.POST)
public String handlePostRequest(HttpServletRequest request, HttpServletResponse response) {
return null;
}
}
And my JSP page url is:
http://localhost.ms.com:8080/DataBox/test/testHarness.jsp
Please give me some suggestions as I am struggling with this since 2 -3 days.

ResourceFilterFactory and non-Path annotated Resources

(I'm using Jersey 1.7)
I am attempting to add a ResourceFilterFactory in my project to select which filters are used per method using annotations.
The ResourceFilterFactory seems to be able to filters on Resources which are annotated with the Path annotation but it would seem that it does not attempt to generate filters for the methods of the SubResourceLocator of the resources that are called.
#Path("a")
public class A {
//sub resource locator?
#Path("b")
public B getB() {
return new B();
}
#GET
public void doGet() {}
}
public class B {
#GET
public void doOtherGet() { }
#Path("c")
public void doInner() { }
}
When ran, the Filter factory will only be called for the following:
AbstractResourceMethod(A#doGet)
AbstractSubResourceLocator(A#getB)
When I expected it to be called for every method of the sub resource.
I'm currently using the following options in my web.xml;
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>com.my.MyResourceFilterFactory</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.my.resources</param-value>
</init-param>
Is my understanding of the filter factory flawed?
You need to use #Path annotation at class level for Class B. When Jersey does the resource scan I bet you that it doesnt pick up this class as a resource.

Resources