Hibernate Validator not invoked on Object Graph - java

I have a model object which has entities annotated with hibernate validation annotations. For example #NotBlank, #NotNull, #Length.
I have a form backing this model which decorates an instance of the model object. I have annotated this instance with an #NotNull, #Valid annotation. I am registering a validator for this backing form in the Controller Class and the form validator is being invoked when the #RequestMapping method argument is annotated with #Valid annotation.
Note the model is also annotated with the #Entity annotation, the model backing form is just
a thin wrapper around the model.
However the validations on the decorated object are not being checked? I know this because in the request mapping method definition I check the BindResult for errors and there are none.
My form fields are all empty, hence the validations on the fields on the decorated model annotated with #NotBlank should be checked. However that does not happen.
Can you help me fix this?
Edit:
Sample Code
#Entity
class MyModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable=false)
private Long id;
#NotBlank
#Column(unique=true, length=30, nullable=false)
private String number;
#Column(length=30, nullable=false)
#Length(min=1, max=30)
private String firstName;
/* ... getters and setters ... */
}
public class MyModelBackingForm {
#NotNull
#Valid
private MyModel model;
/* ... delegate getters and setters for all fields in MyModel ... */
}
Edit:
Add Controller Code
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new MyBackingFormValidator());
}
Edit:
public class MyBackingFormValidator implements Validator {
public MyBackingFormValidator() {
super();
}
#Override
public boolean supports(Class<?> clazz) {
return Arrays.asList(MyBackingForm.class, MyModel.class).contains(clazz);
}
#Override
public void validate(Object obj, Errors errors) {
// custom validation code commented ... as I want to check if JSR 303 validations invoked
}
}

Here is a sample code the way I was using the hibernate validation. I was calling #valid on the fetched objects with the binding result. This way it knows the validations which are required to be done on the form.
Edited: How to invoke custom and standard validator?
Using #InitBinder we can pass the custom validator instance to which again we can pass the class parameter for the standard validator using binder.getValidator()
Courtesy: Rohit Banga
#Controller
#RequestMapping("/customer")
public class CustomerController {
//Edited:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new CustomFormValidator(binder.getValidator()));
}
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public String addCustomer(#Valid Customer customer, BindingResult result) {
if (result.hasErrors()) {
return "form";
} else {
return "success";
}
}
#RequestMapping(method = RequestMethod.GET)
public String customerForm(ModelMap model) {
model.addAttribute("customer", new Customer());
return "form";
}
}

Related

How to configure direct field access on #Valid in Spring?

How can I tell spring-web to validate my dto without having to use getter/setter?
#PostMapping(path = "/test")
public void test(#Valid #RequestBody WebDTO dto) {
}
public class WebDTO {
#Valid //triggers nested validation
private List<Person> persons;
//getter+setter for person
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public static class Person {
#NotBlank
public String name;
public int age;
}
}
Result:
"java.lang.IllegalStateException","message":"JSR-303 validated property
'persons[0].name' does not have a corresponding accessor for Spring data
binding - check your DataBinder's configuration (bean property versus direct field access)"}
Special requirement: I still want to add #AssertTrue on boolean getters to provide crossfield validation, like:
#AssertTrue
#XmlTransient
#JsonIgnore
public boolean isNameValid() {
//...
}
you have to configure Spring DataBinder to use direct field access.
#ControllerAdvice
public class ControllerAdviceConfiguration {
#InitBinder
private void initDirectFieldAccess(DataBinder dataBinder) {
dataBinder.initDirectFieldAccess();
}
}
Try something like this:
#Access(AccessType.FIELD)
public String name;

How to pass an object to a ModelAttrbiute in MockMVC post?

User model:
#Entity
#Table(name="user")
public class User {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#NotBlank
#Column(name="username")
private String username;
#NotEmpty
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="user_role", joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="role_id")})
private Set<Role> roles;
}
Controller:
#RequestMapping(value = {"/users/edit/{id}"}, method = RequestMethod.POST)
public String editUser(ModelMap model, #Valid #ModelAttribute("user") User user, BindingResult result) {
if(result.hasErrors()) {
return "AddUserView";
}
return "redirect:/users";
}
Test with MockMVC:
#Test
public void performUpdateUserTest() throws Throwable {
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("username", "User"));
}
Well, fine, I can pass a param username as always using param(). But what should I do with ROLES? This field is a separate object. I can't pass it using param(). Then how is it possible to pass it in the test?
The only way out I found is to create an entity and pass it using .flashAttr():
#Test
public void performUpdateUserTest() throws Throwable {
User user = new User("User", new HashSet<Role>(Arrays.asList(new Role("USER"))));
mockMvc.perform(post("/users/edit/{id}", user.getId())
.flashAttr("user", user));
}
But then, what if I need to test that user can't be updated because of binding error in the ROLES field(ROLES can't be null, and suppose, it was set as null)? Thus, I'm not able to create user(and use it with .flashAttr) already with a binding error as the exception will be thrown. And I still have to pass it separately.
Well, after a long time of searching, I found out that I should add a converter to the MockMVC. What converter is you can read HERE, for instance.
I had it already in my project but didn't realize that it didn't work with MockMVC.
So, you can add the converter to MockMVC like that:
#Autowired
private StringToRoleConverter stringToRoleConverter;
#Before
public void init() {
FormattingConversionService cs = new FormattingConversionService();
cs.addConverter(stringToRoleConverter);
mockMvc = MockMvcBuilders.standaloneSetup(userController)
.setConversionService(cs)
.build();
}
Converter itself:
#Component
public class StringToRoleConverter implements Converter<String, Role> {
#Autowired
private RoleService roleService;
#Override
public Role convert(String id) {
Role role = roleService.findById(Integer.valueOf(id));
return role;
}
}
And then I can add param like that:
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("roles", "2"))
though I'm passing a string there, it will be converter to Role with the help of Spring converter.

Spring data rest validation for PUT and PATCH running after DB update

I have a SDR project and I am successfully validating the user entity for POST request but as soon as I update an existing entity using either PATCH or PUT the DB is updated BEFORE the validation is executed (the validator is being executed and error is returned but the DB is being updated anyway).
Do I need to setup a separate config for update ? Am I missing an extra step for that?
Entity
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Member {
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_id_gen")
#SequenceGenerator(name = "member_id_gen", sequenceName = "member_id_seq")
#Id
#JsonIgnore
private long id;
#Version
private Integer version;
#NotNull
protected String firstName;
#NotNull
protected String lastName;
#Valid
protected String email;
}
Repository
#RepositoryRestResource(collectionResourceRel = "members", path = "member")
public interface MemberRepository extends PagingAndSortingRepository<Member, Long> {
public Member findByFirstName(String firstName);
public Member findByLastName(String lastName);
}
Validator
#Component
public class BeforeSaveMemberValidator implements Validator {
public BeforeSaveMemberValidator() {}
private String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$";
#Override
public boolean supports(Class<?> clazz) {
return Member.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Member member = (Member) target;
if(ObjectUtils.isEmpty(member.getFirstName())) {
errors.rejectValue("firstName", "member.firstName.empty");
}
if(ObjectUtils.isEmpty(member.getLastName())) {
errors.rejectValue("lastName", "member.lastName.empty");
}
if(!ObjectUtils.isEmpty(member.getDni()) && !member.getDni().matches("^[a-zA-Z0-9]*$")) {
errors.rejectValue("dni", "member.dni.invalid");
}
if(!ObjectUtils.isEmpty(member.getEmail()) && !member.getEmail().matches(EMAIL_REGEX)) {
errors.rejectValue("email", "member.email.notValid");
}
}
}
BeforeSave service
#Service
#RepositoryEventHandler(Member.class)
public class MemberService {
#HandleBeforeCreate
#HandleBeforeSave
#Transactional
public void beforeCreate(Member member) {
...
}
}
I think you should rename your validator, for example, to MemberValidator then assign it as described here:
#Override
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new MemberValidator());
v.addValidator("beforeSave", new MemberValidator());
}
But I suggest you to use Bean validation instead of your custom validators. To use it in SDR project you can inject LocalValidatorFactoryBean, then assign it for 'beforeCreate' and 'beforeSave' events in configureValidatingRepositoryEventListener:
#Configuration
#RequiredArgsConstructor // Lombok annotation
public class RepoRestConfig extends RepositoryRestConfigurerAdapter {
#NonNull private final LocalValidatorFactoryBean validatorFactoryBean;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", validatorFactoryBean);
v.addValidator("beforeSave", validatorFactoryBean);
super.configureValidatingRepositoryEventListener(v);
}
}
In this case your SDR will automatically validate payloads of POST, PUT and PATCH requests for all exposed SDR repositories.
See my example for more details.

Validator for MethodArgumentNotValidException only handles constraint of same type

I'm trying to validate my form against constraints set on my bean. Spring-MVC version i am using is 3.2.4. The problem is that default Spring validator does not validate all constraints; only the ones that are of the same type.
I have the following controller code:
#Controller
#SessionAttributes()
public class FormSubmitController {
#RequestMapping(value = "/saveForm", method = RequestMethod.POST)
#ResponseBody
public ModelMap saveForm(#Valid #RequestBody Form form, HttpSession session) {
session.setAttribute("form", form);
ModelMap map = new ModelMap();
map.addAttribute("hasErrors", false);
return map;
}
}
and the following bean:
public class Form implements IForm, Serializable {
#NotNull(message = "Category should not be empty")
protected String category;
#NotNull(message = "Sub-Category should not be empty")
protected String subCategory;
#Size(min=0, message="Firstname should not be empty")
protected String firstName;
#Size(min=0, message="Lastname should not be empty")
protected String lastName;
#Pattern(regexp="^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\\d\\d$", message="Date of birth should be in dd-mm-jjjj format")
protected String dateOfBirth;
//getters and setters
}
The handler for MethodArgumentNotValidException looks like this:
#ControllerAdvice
public class FormExceptionController {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ModelMap handleMethodArgumentNotValidException(MethodArgumentNotValidException error) {
List<FieldError> errors = error.getBindingResult().getFieldErrors();
ModelMap map = new ModelMap();
ModelMap errorMap = new ModelMap();
map.addAttribute("hasErrors", true);
for (FieldError fieldError : errors) {
errorMap.addAttribute(fieldError.getField(), fieldError.getDefaultMessage());
}
map.addAttribute("bindingErrors", errorMap);
return map;
}
}
So, an empty form results in the first two error messages.
The firts two properties of the form filled results in the third and fourth error messages.
Only when i use the same contraint type (i.e. NotNull) for all properties on my bean it will return all error messages.
What can be wrong here?
Nothing is wrong the validator for #Size and #Pattern by default accept null as valid. So you actually need both annotations (#NotNull and #Pattern/#Size). These annotations only trigger validation of values which are non-null, these validations don't imply that null values are invalid that is where the #NotNull is for.
This is assuming you are using hibernate-vaildator (as that is the out-of-the-box supported validator).

How to validate two or more beans in a Spring Controller method with Hibernate Validator (JSR 303)

I have two classes (Beans)
public class BeanOne {
#Min(1)
private Integer idBeanOne;
#NotBlank
private String nameBeanOne;
#NotNull
#Min(1)
private Integer idOther;
// ... Getters and Setters
}
public class BeanTwo {
#Min(1)
private Integer idBeanTwo;
#NotBlank
private String nameBeanTwo;
#NotNull
#Min(1)
private Integer idOtherTwo;
// ... Getters and Setters
}
Controller of Spring
// Method in Controller
#RequestMapping(value = "/name.html", method = RequestMethod.POST)
public #ResponseBody
Map<String, Object> submitInsert(#Valid BeanOne one,
#Valid BeanTwo two, BindingResult result) {
if (result.hasErrors()) {
// Errores
} else {
// :D
}
}
Is there any way that I can validate two or more beans? I have successfully validated a single bean, but I have not been successful in validating two or more beans. How can I do this?
thanks: D
thanks: D
After many attempts to validate two or more beans with JSR303, come to this solution.
public class BeanOne {
#Valid
private BeanTwo beanTwo;
// other beans to validate
#Valid
private BeanN beanN;
#Min(1)
private Integer idBeanOne;
#NotBlank
private String nameBeanOne;
#NotNull
#Min(1)
private Integer idOther;
// ... Getters and Setters
}
public class BeanTwo {
#Min(1)
private Integer idBeanTwo;
#NotBlank
private String nameBeanTwo;
#NotNull
#Min(1)
private Integer idOtherTwo;
// ... Getters and Setters
}
// Controller Spring
#Controller
public class XController {
#Autowired
private Validator validator;
#RequestMapping(value = "/name.html", method = RequestMethod.POST)
public #ResponseBody Map<String, Object>
submitInsert(BeanOne beanOne, BeanTwo beanTwo, BindingResult result) {
beanOne.setBeanTwo(beanTwo);
// beanOne.setBeabN(beanN);
validator.validate(beanOne, result);
if (result.hasErrors()) {
// Errores
} else {
// :D
}
}
// more code ...
}
But now I have another problem :(
I have this file Messages.properties
typeMismatch.java.lang.Integer = Must specify an integer value.
typeMismatch.java.lang.Long = Must specify an integer value.
typeMismatch.java.lang.Float = Must specify a decimal value.
typeMismatch.java.lang.Double=Must specify a decimal value.
This file helps me to catch exceptions, when a field expects a number, and the user enters text
This works perfectly for the first bean (BeanOne) but not for nested beans (BeanTwo, BeanN)
I hope they can help me: D
thanks

Categories

Resources