user4080732
user4080732

Reputation: 157

Spring, get multiple model violations from exception

I am trying to validate user registration. Scenario is common: If username or e-mail exist, then application must notify the user, which field was violated (Unique constraint). So far I have done the following: This method handles registration. Basically I try to commit transaction (insert) and if it fails, exception is caught with try catch.

@RequestMapping(value = "/registration", method = RequestMethod.POST)
public ModelAndView createNewUser(@Valid User user, BindingResult bindingResult) {
    ModelAndView modelAndView = new ModelAndView();
    try{
        userService.saveUser(user);
        modelAndView.addObject("successMessage", "User has been registered successfully");
        modelAndView.addObject("user", user);
        modelAndView.setViewName("registration");
    }catch(DataIntegrityViolationException e){
        bindingResult
        .rejectValue("email", "error.user",
                "There is already a user registered with the email provided");
    }
    return modelAndView;
}

This is my model class:

@Entity
@Table(name = "users")
public class User implements Serializable{

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="user_id")
private Long user_id;

@Column(name="username")
@NotEmpty(message = "*Username is required")
@Length(min = 5, max = 20,  message = "*Your username can have min 5 and max 20 characters")
private String username;

@Column(name="password")
@NotEmpty(message = "*Password is reqired")
@Transient
private String password;

@Column(name="email")
@Email(message = "*Please provide a valid Email")
@NotEmpty(message = "*Email is required")
private String email;

@Column(name="enabled")
private int enabled;

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public int getEnabled() {
    return enabled;
}

public void setEnabled(int enabled) {
    this.enabled = enabled;
}

public Long getUser_id() {
    return user_id;
}

public void setUser_id(Long user_id) {
    this.user_id = user_id;
}
}

And this is my registration page:

<form autocomplete="off" action="#" th:action="@{/registration}"
                th:object="${user}" method="post" class="form-horizontal"
                role="form">
                <h2>Registration Form</h2>
                <div class="form-group">
                    <div class="col-sm-9">
                    <label th:if="${#fields.hasErrors('username')}" th:errors="*{username}"
                            class="validation-message"></label>
                    <input type="text" th:field="*{username}" placeholder="Username"
                            class="form-control" /> 
                    </div>
                </div>

                <div class="form-group">
                    <div class="col-sm-9">
                        <input type="text" th:field="*{email}" placeholder="Email"
                            class="form-control" /> <label
                            th:if="${#fields.hasErrors('email')}" th:errors="*{email}"
                            class="validation-message"></label>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-sm-9">
                        <input type="password" th:field="*{password}"
                            placeholder="Password" class="form-control" /> <label
                            th:if="${#fields.hasErrors('password')}" th:errors="*{password}"
                            class="validation-message"></label>
                    </div>
                </div>

                <div class="form-group">
                    <div class="col-sm-9">
                        <button type="submit" class="btn btn-primary btn-block">Register User</button>
                    </div>
                </div>
                <span th:utext="${successMessage}"></span>
            </form>

Problem. Now the thing is that in my createNewUser method I do try catch. When unique violation occurs, catch block handles it. But what if there is multiple violations: username and email? How do I get from exception, which field was violated and associate it with right form field?

Upvotes: 0

Views: 311

Answers (1)

StanislavL
StanislavL

Reputation: 57381

I would say you need to add your own custom validators to check unique username and email.

Code snipped from the example below. @Unique(service = UserService.class, fieldName = "email", message = "{email.unique.violation}") private String email;

Annotation and validator

@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueValidator.class)
@Documented
public @interface Unique {
    String message() default "{unique.value.violation}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    Class<? extends FieldValueExists> service();
    String serviceQualifier() default "";
    String fieldName();
}

and

public class UniqueValidator implements ConstraintValidator<Unique, Object> {
    @Autowired
    private ApplicationContext applicationContext;

    private FieldValueExists service;
    private String fieldName;

    @Override
    public void initialize(Unique unique) {
        Class<? extends FieldValueExists> clazz = unique.service();
        this.fieldName = unique.fieldName();
        String serviceQualifier = unique.serviceQualifier();

        if (!serviceQualifier.equals("")) {
            this.service = this.applicationContext.getBean(serviceQualifier, clazz);
        } else {
            this.service = this.applicationContext.getBean(clazz);
        }
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        return !this.service.fieldValueExists(o, this.fieldName);
    }
}

See the example

Upvotes: 1

Related Questions