Spring field validation not binded in BindingResult

Viewed 66

I want to add a simple field validator (start date must be before end date) to my form. Unfortunatelly the validation is not binded to BindingResult and I'm receiving 500 all the time. My controllet let's it through and crashes during the attempt to save the entity. Maybe it is caused by the fact, that the validation is on a Task class, where the object processed in the form is of type Project with the property:

  • Timetable

And there in the Timetable you will find:

  • List<Task>

The error message:

Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [lukaur.grant_management_system.app.web.model.project.timetable.Task] during update time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='Start date must be before the end date', propertyPath=, rootBeanClass=class lukaur.grant_management_system.app.web.model.project.timetable.Task, messageTemplate='Start date must be before the end date'}
]

Entity:

@Entity
@Data
@ToString(of = "name")
@EqualsAndHashCode
@DateComparator
public class Task {

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

    private String name;
    private String description;
    private Date taskStart;
    private Date taskEnd;
}

Controller:

@PostMapping("/save")
public String processProjectSave(@Valid Project project,
                                 BindingResult result,
                                 @RequestParam Map<String, String> allParams) {
    if (result.hasErrors()) {
        log.error("Cant save a project");
        return "project/project";
    }
    log.info("Attempting to save project");
    projectsService.save(project);
    log.info("Project saved");
    return "redirect:/menu";
}

Annotation on the validation:

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateCompareValidator.class)
public @interface DateComparator {
    String message() default "Start date must be before the end date";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Validator:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class DateCompareValidator implements ConstraintValidator<DateComparator, Task> {

    @Override
    public void initialize(DateComparator constraint) {
    }

    @Override
    public boolean isValid(Task task, ConstraintValidatorContext context) {
            return task.getTaskStart().before(task.getTaskEnd());
   }
}

Edit: Project and Timetable entities:

Project:

@Entity
@Data
@ToString(of = "name")
@EqualsAndHashCode(of = "id")

public class Project {

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

    @OneToOne(cascade = CascadeType.ALL)
    private Timetable timetable;


    (...other fields...)

}

Timetable:

@Entity
@Data
@ToString(of = "id")
@EqualsAndHashCode(of = "id")
public class Timetable {

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

    @OneToMany
    List<Task> tasks = new ArrayList<>();

    private LocalDate projectStart;

    private LocalDate projectEnd;

}

the form itself:

<form th:action="@{/project/save}" method="post" th:object="${project}" id="projectForm">

(...other code...)

<h3>6 Timetable</h3>
    <input type="number" th:field="*{timetable.id}" hidden/>

    <div th:each="task, stat : *{timetable.tasks}" class="taskDiv">
        <h3>Task no <span th:text="${stat.count}"></span></h3>
        <input type="number" th:field="*{timetable.tasks[__${stat.index}__].id}" hidden>
        <button class="button removeTaskButton">Remove</button>
        <p>Task name</p>
        <label>
            <input type="text" th:field="*{timetable.tasks[__${stat.index}__].name}">
        </label>
        <p>Task description</p>
        <label>
                            <textarea th:field="*{timetable.tasks[__${stat.index}__].description}"
                                      th:text="*{timetable.tasks[__${stat.index}__].description}" cols="100"
                                      rows="4"></textarea>
        </label>
        <p>Start date</p>
        <label>
            <input class="dateInput taskStart" type="date"
                   th:field="*{timetable.tasks[__${stat.index}__].taskStart}">

            <p>End date</p>

            <input class="dateInput taskEnd" type="date"
                   th:field="*{timetable.tasks[__${stat.index}__].taskEnd}">
        </label>

(...other code...)

</form>

Edit no 2: screenshot added, though Im not sure if it will be usefull: enter image description here

0 Answers
Related