Trying to simply validate a field of my bean and instead of doing it manually wanted to check out Spring Validation, but didn't have much luck as of now.
In short:
Validation with #Valid annotation seems to never be called when I call a method of my #RestController
My code:
pom.xml (for the validation part)
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
Spring is version 4.1.1
Validator
package mypackage;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
public class UtenteValidator implements Validator{
public UtenteValidator() {
// TODO Auto-generated constructor stub
}
#Override
public boolean supports(Class<?> clazz) {
return UtenteLite.class.equals(clazz);
}
//validation test
#Override
public void validate(Object target, Errors errors) {
UtenteLite user = (UtenteLite) target;
if(user.getName != "foo") {
errors.rejectValue("name", "name not correct");
}
}
}
Controller
package myPackage;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/users")
public class UsersController {
public UsersController() {
}
//tried to put #InitBinder, but no luck
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UtenteValidator());
}
#ResponseBody
#RequestMapping(value="", method=RequestMethod.PUT)
public <T> ResponseEntity<T> aggiornaUtente(#RequestBody #Valid UtenteLite utente, BindingResult result)
{
ResponseEntity<T> responseEntity=null;
return responseEntity;
}
}
Te BindingResult result object shows always zero errors and the validate, supports or initBinder methods are never called.
Found this tutorial that reads:
When #InitBinder methods get called?
The #InitBinder annotated methods
will get called on each HTTP request if we don't specify the 'value'
element of this annotation.
WebDataBinder argument is specific to a model attribute. That means
each time a model attribute is created by Spring this method will get
called with a new instance of WebDataBinder.
So I tried to change my controller method to this adding a #ModelAttribute and NOW the validation code gets called BUT the requestBody object (the "utente" object) is empty, so validation always fails because the fields are all nulls:
#ResponseBody
#RequestMapping(value="", method=RequestMethod.PUT)
public <T> ResponseEntity<T> aggiornaUtente(#RequestBody #Valid #ModelAttribute("utente") UtenteLite utente, BindingResult result)
{
...
}
The utente method parameter is passed with a JSON as the body of the request.
Ok,
after several tries I succeded in producing a working solution just by adding the hibernate-validation artifact reference in my pom.xml.
I wrongly supposed the hibernate-validator was mandatory only if I was usig validation annotations on the beans properties (like #NotNull, #Pattern, etc..)
So only by adding this snippet I was able to solve my problem (hope this will spare a few hours of work to someone else):
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
The complete code now is:
Validator
package mypackage;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
public class UtenteValidator implements Validator{
public UtenteValidator() {
// TODO Auto-generated constructor stub
}
#Override
public boolean supports(Class<?> clazz) {
return UtenteLite.class.equals(clazz);
}
//validation test
#Override
public void validate(Object target, Errors errors) {
UtenteLite user = (UtenteLite) target;
if(user.getName != "foo") {
errors.rejectValue("name", "name not correct");
}
}
}
Controller
package myPackage;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/users")
public class UsersController {
public UsersController() {
}
//tried to put #InitBinder, but no luck
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UtenteValidator());
}
#ResponseBody
#RequestMapping(value="", method=RequestMethod.PUT)
public <T> ResponseEntity<T> aggiornaUtente(#RequestBody #Valid UtenteLite utente)
{
ResponseEntity<T> responseEntity=null;
return responseEntity;
}
}
Related
I built my Spring boot 2.x application using this demo:
https://spring.io/guides/gs/spring-boot/
The problem I'm having is that when there's an exception in Spring/Spring boot, it is printed to standard output. I don't want that. I want to capture them and do other processing such as logging them. I can capture the exceptions of my code but I can't capture the exceptions of Spring/Spring boot. Therefore, how do I capture all exceptions of Spring/Spring Boot 2.x so I can handle them? Is there an easy way to do this, like a generic exception catcher? Can someone show me some code?
My Code:
1. Example.java
package example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
#SpringBootApplication
public class Example extends SpringBootServletInitializer {
public static void main(String[] args) throws Exception {
SpringApplication.run(Example.class, args);
}
}
2. ExampleController.java
package example;
import org.springframework.stereotype.Controller;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class ExampleController {
#GetMapping ("/example")
public ModelAndView example()
{
MyData data= new MyData();
data.setName("Example");
data.setVersion("1.0");
ModelAndView model = new ModelAndView("page");
model.addObject("page", data);
return model;
}
}
3. GeneralExceptionHandler.java
package example;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
class GeneralExceptionHandler {
#ExceptionHandler(Exception.class)
public void handleException() {
System.out.println("Exception handler");
}
}
4. MyData.java
package example;
import lombok.Data;
#Data
public class MyData {
private String name;
private String version;
}
5. page.jsp
<!DOCTYPE html>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
<form:form id="myForm" class="form-horizontal"
modelAttribute="page">
<form:label id="name" class="control-label" path="name" for="name">
${page.name}</form:label>
<!-- Error introduced on purpose to force exception. "version123" should be just "version" -->
<form:label id="version" class="control-label" path="version" for="version">
${page.version123}</form:label>
</form:form>
</html>
You can use #ExceptionHandler annotation to catch a specific Exception, to do that you can simply annotate a method inside your controller with #ExceptionHandler and provide it with a specific exception, example :
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleException(){
// do some thing here
}
The limite of this way of doing is that it will handle only exceptions thrown by the #RequestMapping where the #ExceptionHandler is declared. To avoid this limitation you can use a Controller advice which allows you to use exactly the same exception handling techniques but apply them across the whole application, example using controller advice:
#ControllerAdvice
class GeneralExceptionHandler {
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleException() {
// Do some thing here
}
}
hint : if you want to catch all checked exceptions you can use #ExceptionHandler(Exception.class)
You can use AOP with pointcut on all public method in Controller.
I use BaseController as base class for all my Controllers.
#RestController
#RequestMapping(value = "/api/application")
public class ApllicationController extends ApiController {
//class body with method
}
Then add AOP handling all exceptions throw from public methods in Controller.
#Aspect
#Configuration
public class AOP_ApiController {
#Pointcut("execution (public * *(..))")
private void anyPublicMethod() {}
#Pointcut("execution (* api.ApiController+.*(..))")//here package and class name as base class
private void methodOfApiController() {}
#AfterThrowing(value="methodOfApiController() && anyPublicMethod()", throwing="exception")
public void afterThrowingExceptionFromApiController(JoinPoint joinPoint, Exception exception) throws Exception {
ApiController controller = getController(joinPoint);
String methodName=getMethodName(joinPoint);
logException(exception, controller, methodName);
throw exception;
}
private ApiController getController(JoinPoint joinPoint) {
return (ApiController) joinPoint.getTarget();
}
private String getMethodName(JoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}
private void logException(Exception ufeException, ApiController controller, String methodName) {
//log exception as you want
}
}
I have an application with two packages. In the first package, I have 2 controllers. The first controller is called APIController.java which displays a view. This is the code of the controller:
package com.dogo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
#RequestMapping()
public class APIController {
#RequestMapping("/api")
public String apiChat() {
return "apiChat.html";
}
}
The second controller is called HomeController.java which I use to display the index.html page. This is the code:
package com.dogo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class HomeController {
#RequestMapping("/")
public String index() {
return "index.html";
}
}
The second package also contains a controller called MessageController.java. This is used to update a dao database based on the parameters passed in the url. This is the code for this controller:
package com.dogo.chat;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping()
public class MessageController {
#Autowired
MessageRepository dao;
#GetMapping("/chat")
public List<Message> getMessages(){
List<Message> foundMessages = dao.findAll();
return foundMessages;
}
#PostMapping("/chat")
public ResponseEntity<Message> postMessage(#RequestBody Message message)
{
// Saving to DB using an instance of the repo Interface.
Message createdMessage = dao.save(message);
// RespEntity crafts response to include correct status codes.
return ResponseEntity.ok(createdMessage);
}
#GetMapping("/chat/{id}")
public ResponseEntity<Message> getMessage(#PathVariable(value="id")
Integer id){
Message foundMessage = dao.findOne(id);
if(foundMessage == null) {
return ResponseEntity.notFound().header("Message","Nothing found with that id").build();
}
return ResponseEntity.ok(foundMessage);
}
#DeleteMapping("/message/{id}")
public ResponseEntity<Message> deleteMessage(#PathVariable(value="id") Integer id){
Message foundMessage = dao.findOne(id);
if(foundMessage == null) {
return ResponseEntity.notFound().header("Message","Nothing found with that id").build();
}else {
dao.delete(foundMessage);
}
return ResponseEntity.ok().build();
}
}
When I type http://localhost:8080/api into my browser, the apiChat.html page is displayed. When I change the #RequestMapping() in APIController.java to #RequestMapping("/api") and I type http://localhost:8080/api/api I get a 404 error. I had expected the apiChat.html page to be displayed. What am I missing? Thank you.
I have this class, which is part of a SpringBoot Application
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springboot.tutorial.upwork.service.WelcomeService;
#RestController
public class WelcomeController {
#Autowired
private WelcomeService service;
// http://localhost:7777/welcome
#RequestMapping("/welcome")
public String welcome() {
return service.retrieveWelcomeMessage();
}
// http://localhost:7777/welcome/bla
#RequestMapping("/welcome/{msg}")
public String welcome(#PathVariable("msg") String message) {
return service.retrieveWelcomeMessage(message);
}
}
Now i need to add a variable to hold some data, which will be available to class methods. How can i do it since an instance of this class is created by Spring?
This is my code:
package com.application.myGoogleAppEngine.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
#Controller
public class IndexController {
#RequestMapping("/index")
public String index(ModelMap map){
return "index";
}
}
Unfornately, #RequestMapping makes an error. I use Spring 2.5.6 and I've imported org.springframework and org.springframework with spring-webmvc in my pom.xml
Do you have any solutions ?
Thank you
Just add the import statement for that class
import org.springframework.web.bind.annotation.RequestMapping;
I have a simple PersonController class that provides save() method to persist the object from http post request.
package org.rw.controller;
import java.sql.Timestamp;
import java.util.List;
import org.rw.entity.Person;
import org.rw.service.PersonService;
import org.rw.spring.propertyeditor.TimestampPropertyEditor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
#RequestMapping(value="/person")
public class PersonController {
private static final Logger logger = LoggerFactory.getLogger(PersonController.class);
#Autowired
private PersonService personService;
#Autowired
TimestampPropertyEditor timestampPropertyEditor;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Timestamp.class, "dob", timestampPropertyEditor);
}
#RequestMapping(value="/save", method=RequestMethod.POST)
public String save(Model model, Person person) {
Long personId = personService.save(person);
return "redirect:view/" + personId;
}
}
As the save() method returns as return "redirect:view/" + personId;. It will be diffrerent for every request. it may be like "view/5" or "view/6" depending on the id of the object that has been persisted.
Then i have a simple class to test the above controller with spring mocking.
package org.rw.controller;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.rw.service.UserService;
import org.rw.test.SpringControllerTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
public class PersonControllerTest extends SpringControllerTest {
#Autowired
private UserService userService;
#Test
public void add() throws Exception {
mockMvc.perform(get("/person/add", new Object[0])).andExpect(status().isOk());
}
#Test
public void save() throws Exception {
UserDetails userDetails = userService.findByUsername("anil");
Authentication authToken = new UsernamePasswordAuthenticationToken (userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
mockMvc.perform(
post("/person/save", new Object[0])
.param("firstName", "JunitFN")
.param("lastName", "JunitLN")
.param("gender", "M")
.param("dob", "11/02/1989")
).andExpect(
redirectedUrl("view")
);
}
}
now here i have a problem that redirectedUrl("view") is rejecting value "view/5". I have tried redirectedUrl("view*") and redirectedUrl("view/*") but its not working.
Edit :
Here I have got a workaround as per below
MvcResult result = mockMvc.perform(
post("/person/save", new Object[0])
.param("firstName", "JunitFN")
.param("lastName", "JunitLN")
.param("gender", "MALE")
.param("dob", "11/02/1989")
).andExpect(
//redirectedUrl("view")
status().isMovedTemporarily()
).andReturn();
MockHttpServletResponse response = result.getResponse();
String location = response.getHeader("Location");
Pattern pattern = Pattern.compile("\\Aview/[0-9]+\\z");
assertTrue(pattern.matcher(location).find());
but still i am looking for the proper way.
update:
I have posted the same issue on spring jira here :
Since spring 4.0 you can use redirectedUrlPattern as pointed by Paulius Matulionis
As of spring 3.x this is not supported out of the box but you can easily add you custom result matcher
private static ResultMatcher redirectedUrlPattern(final String expectedUrlPattern) {
return new ResultMatcher() {
public void match(MvcResult result) {
Pattern pattern = Pattern.compile("\\A" + expectedUrlPattern + "\\z");
assertTrue(pattern.matcher(result.getResponse().getRedirectedUrl()).find());
}
};
}
And use it like build-in matcher
mockMvc.perform(
post("/person/save", new Object[0])
.param("firstName", "JunitFN")
.param("lastName", "JunitLN")
.param("gender", "M")
.param("dob", "11/02/1989")
).andExpect(
redirectedUrlPattern("view/[0-9]+")
);
Since 4.0 it is available in Spring itself.
Please check here.