1

I'm trying to simplify my code by binding request parameters directly to User Entity instead of copying value field by field, but I can't seem to get validation working that way.

Controller:

    @Autowired
    private UserValidator validator;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(validator);
    }

    @PreAuthorize("isAnonymous()")
    @RequestMapping("/register")
    public List<String> register(@Valid @ModelAttribute RegisterRequest request, BindingResult result){
        return result.getAllErrors().stream().map(error -> error.getCodes()[1]).collect(Collectors.toList());
    }

Validator:

@Component
public class UserValidator implements Validator {

    @Autowired
    private UserRepository repo;

    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.equals(RegisterRequest.class)||clazz.equals(User.class);
    }

    @Override
    public void validate(Object target, Errors errors) {
        if(target instanceof RegisterRequest){
            RegisterRequest request = (RegisterRequest)target;
            if(isRegistered(request))
                errors.rejectValue("User.Email", "InUse");
            if(!isRepassCorrect(request))
                errors.rejectValue("Repassword", "NoMatch");
        }

    }

    private boolean isRegistered(RegisterRequest request){
        return repo.findByEmail(request.getUser().getEmail())!=null;
    }

    private boolean isRepassCorrect(RegisterRequest request){
        return request.getPassword().equals(request.getRepassword());
    }

}

Models (note that nested object is annotaded with @Valid):

public class RegisterRequest {

    @Valid
    private User user = new User();

    @NotEmpty
    private String password;

    @NotEmpty
    private String repassword;

    //Generated getters and setters for all fields

}

@Entity
@Table(name = "users")
public class User implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 3726459440738726042L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @NotEmpty
    @Email
    @Column(name = "email", nullable = false)
    private String email;

    @NotEmpty
    @Column(name = "hash", nullable = false)
    private String hash;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(referencedColumnName = "id")
    private UserRole role;

    @Column(name = "enabled", nullable = false)
    private boolean enabled;

    @NotEmpty
    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "company")
    private String company;

    //Getters, setters, equals and hashCode

}

Problem is that only password and repassword fields are properly validated, validation annotations in User class are ignored and any request with just matching passwords passes while it should rise errors. Empty passwords or different ones rise errors as expected.

2
  • 1
    Ofcourse it won't work. You register a custom validator which disables JSR-330 validation. Instead of setValidator use addValidator. Commented Sep 29, 2017 at 8:30
  • That was it. adding validator instead of replacing it works as expected. thanks a lot. Commented Sep 29, 2017 at 8:42

1 Answer 1

2

In you initBinder use addValidator method instead of setValidator and also tell it which MaV object to validate by adding a MaV name to the annotations:

@InitBinder("request")

and

... @Valid @ModelAttribute("request") RegisterRequest request
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.