1

This is my Spring boot project set up.Project Set up

I have a standard Spring boot JPA stack in which I am trying to validate the state of the instance variables. My bean is as follows:

Configuration

@Component
public class ConfigurationHelper {

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("messages");
        messageSource.setUseCodeAsDefaultMessage(true);
        messageSource.setCacheSeconds(5);
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public Validator validator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setValidationMessageSource(this.messageSource());
        return factoryBean;
    }
}

Model class

@Table(name = "user_station", indexes = {
    @Index(columnList = "station_id", name = "station_index_station_id"),
    @Index(columnList = "station_name", name="station_index_name"),
    @Index(columnList = "hd_enabled", name = "station_index_hd_enabled")
})
@Entity
@EqualsAndHashCode(exclude = {"createdTimeStamp", "updatedTimestamp"}, callSuper = true)
@ToString(exclude = {"createdTimeStamp", "updatedTimestamp"}, callSuper = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Station extends IError {

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

    // Represents a station id for the user.
    @Column(name="station_id", nullable = false, unique = true)
    @NotEmpty(message = "{station.id.empty}")
    @Pattern(regexp = "^K|W[A-Za-z0-9\\-].*$", message = "{station.id.name.not.valid}")
    private String stationId;

    @Column(name="station_name", nullable = false)
    @JsonProperty("name")
    @NotEmpty(message = "{station.name.empty}")
    private String stationName;

    @Column(name = "hd_enabled")
    private Boolean hdEnabled;

    @Column(name="call_sign", nullable = false)
    @NotEmpty(message = "{station.call.sign.empty}")
    private String callSign;

    @Column(name="user_created_timestamp")
    @JsonIgnore
    private LocalDateTime createdTimeStamp;

    @Column(name="user_modified_timestamp")
    @JsonIgnore
    private LocalDateTime updatedTimestamp;

    /**
     * Initialises the timestamps prior to update or insertions.
     *
     * <p>The implementation ensures that time stamps would always reflect the time when entities
     * were persisted or updated.
     */
    @PrePersist
    @PreUpdate
    public void setTimestamps() {
        LocalDateTime utcNow = LocalDateTime.now(ZoneOffset.UTC);
        if (this.createdTimeStamp == null) {
            this.createdTimeStamp = utcNow;
        }
        this.updatedTimestamp = utcNow;
        if (this.hdEnabled == null) {
            this.hdEnabled = Boolean.FALSE;
        }
    }

    // Getters, Setters, Equals and HashCode functions.
}

Unit tests

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes= {App.class})
public class StationTest {

    @Autowired
    private Validator validator;

    private Station station;

    @Before
    public void setUp() throws Exception {
        this.station = new Station();
    }


    @Test
    public void testValidator_allNulls() {
        Set<ConstraintViolation<Station>> constraintViolations =
            this.validator.validate(this.station);
        MatcherAssert.assertThat(constraintViolations.isEmpty(), Is.is(false));
        for (ConstraintViolation<Station> constraintViolation : constraintViolations) {
            System.out.println(constraintViolation.getMessage());
        }
    }
}

This is my output

. . . . Rest of the stack trace omitted . . . .

     2018-12-01 23:30:38.532  INFO 21297 --- [           main] com.iheartmedia.model.StationTest        : Starting StationTest on Kartiks-MacBook-Pro-2.local with PID 21297 (started by krishnanand in /Users/krishnanand/projects/iheartmedia)
2018-12-01 23:30:38.533 DEBUG 21297 --- [           main] com.iheartmedia.model.StationTest        : Running with Spring Boot v2.0.5.RELEASE, Spring v5.0.9.RELEASE
2018-12-01 23:30:38.539  INFO 21297 --- [           main] com.iheartmedia.model.StationTest        : No active profile set, falling back to default profiles: default
2018-12-01 23:30:38.596  INFO 21297 --- [           main] o.s.w.c.s.GenericWebApplicationContext   : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@6892b3b6: startup date [Sat Dec 01 23:30:38 PST 2018]; root of context hierarchy
2018-12-01 23:30:39.565  INFO 21297 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$b7d31eab] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-12-01 23:30:39.730  INFO 21297 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2018-12-01 23:30:39.871  INFO 21297 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2018-12-01 23:30:39.903  INFO 21297 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2018-12-01 23:30:39.917  INFO 21297 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
2018-12-01 23:30:40.030  INFO 21297 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.2.17.Final}
2018-12-01 23:30:40.031  INFO 21297 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2018-12-01 23:30:40.066  INFO 21297 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2018-12-01 23:30:40.196  INFO 21297 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2018-12-01 23:30:40.652  INFO 21297 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2018-12-01 23:30:40.985  INFO 21297 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
2018-12-01 23:30:41.351  INFO 21297 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-12-01 23:30:41.586  INFO 21297 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext@6892b3b6: startup date [Sat Dec 01 23:30:38 PST 2018]; root of context hierarchy
2018-12-01 23:30:41.624  WARN 21297 --- [           main] aWebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2018-12-01 23:30:41.658  INFO 21297 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/iheartmedia/stations],methods=[GET]}" onto public java.util.List<com.iheartmedia.model.Station> com.iheartmedia.controller.StationController.retrieveAllStations()
2018-12-01 23:30:41.660  INFO 21297 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/iheartmedia/station],methods=[POST]}" onto public org.springframework.http.ResponseEntity<com.iheartmedia.dto.StationMixin> com.iheartmedia.controller.StationController.createStation(com.iheartmedia.model.Station,org.springframework.validation.Errors)
2018-12-01 23:30:41.660  INFO 21297 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/iheartmedia/station],methods=[DELETE]}" onto public org.springframework.http.ResponseEntity<com.iheartmedia.dto.StationMixin> com.iheartmedia.controller.StationController.deleteStation(com.iheartmedia.model.Station)
2018-12-01 23:30:41.663  INFO 21297 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-12-01 23:30:41.664  INFO 21297 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-12-01 23:30:41.687  INFO 21297 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-12-01 23:30:41.687  INFO 21297 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-12-01 23:30:41.979  INFO 21297 --- [           main] com.iheartmedia.model.StationTest        : Started StationTest in 3.704 seconds (JVM running for 4.431)
{station.call.sign.empty}
{station.name.empty}
{station.id.empty}
2018-12-01 22:11:10.360  INFO 18663 --- [       Thread-2] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@6892b3b6: startup date [Sat Dec 01 22:11:06 PST 2018]; root of context hierarchy
2018-12-01 22:11:10.363  INFO 18663 --- [       Thread-2] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2018-12-01 22:11:10.364  INFO 18663 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2018-12-01 22:11:10.367  INFO 18663 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Process finished with exit code 0

messages.properties Resource bundle

station.not.found=Station {0} was not found
station.id.empty=Station ID can not be empty.
station.id.format.not.valid=Station ID ${validatedValue} is not valid. Station ID should start with either W or K.
station.name.empty=Station name can not be empty.
station.call.sign.empty=Station call sign can not be empty.

Our dependency tree

Project Dependency Tree

I have read the following

  1. Custom error messaging on Hibernate Validation

  2. Custom Message Key in Hibernate validator not working with message.property

  3. Does Spring Boot automatically resolve message keys in javax and hibernate validation annotations

but I can not still figure out what I am doing wrong.

UPDATE

Upon @Jonathan Johx's suggestion, I changed the basename of the ResourceBundleMessage to messages (Updated the code snippet as well), but I still get the error.

Kartik
  • 2,541
  • 2
  • 37
  • 59
  • I guess in your Test class `Station` object members was null. (As it is created using `this.station = new Station()` ). And you already tell in validator rules that can't be empty. e.g: `stationId`, `stationName`. Correct me if I am wrong. – Ataur Rahman Munna Dec 02 '18 at 07:12
  • It is intended to be null. That is why the validation failed. But the message keys are not being looked up for some reason. – Kartik Dec 02 '18 at 07:21
  • Can you try `messageSource.setBasename("classpath:messages");`? – Ataur Rahman Munna Dec 03 '18 at 04:34

1 Answers1

2

The issue is because the classpath: does reference to root of folder which is ConfigurationHelper class, it's not found. Try to rename file name to ValidationMessages.properties which is used by Validator and update the following line:

messageSource.setBasenames("ValidationMessages"); 

UPDATED

If you want to use validation messages by default then you have to create a file called:

ValidationMessages.properties

And add properties that you consider necessary.

Jonathan JOhx
  • 5,784
  • 2
  • 17
  • 33