Jungmin Lee
Jungmin Lee

Reputation: 41

How can I use external files as resources for MessageSource?

I created validation annotation classes such as @IpV4, @IpV4List in library packages. These annotations return the key (code) that can be used in the MessageSource when the validation is violated.

message.properties files are in the common resource folder, and each module that uses annotations in library package will attempt to specify the path when registering MessageSource bean.

However, when an annotation raises an exception such as MethodArgumentNotValidException, MessageSource does not find any messages with message template (code defined in the annotation).

I have tried most of the methods posted on StackOverFlow related to this issue.

  1. basename in application.properties (without static-location)
spring.messages.basename=messages/messages, file:/path/to/file
  1. basename in @Configuration class (without static-location)
@Bean
public MessageSource messageSource(){
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasenames("messages/messages", "file:/path/to/file");
    // some other settings...
    return messageSource;
}
  1. static resource location in application.properties
spring.messages.basename=messages/messages, messages/vailidation_messages
spring.resources.static-locations=file:/path/to/parent_of_messages
  1. static resource location in ```@Configuration` class
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("file:/path/to/parent_of_messages")
    // some other settings
    ;
}

I tried many other approaches, but I could not get a message from calling MessageSource.getMessage(), and ConstraintViolationException occurred.

The structure of my projects is as follows.

foo-cloud
│
├─ foo-config
│  ├─ resources
│  │  ├─ messages << message properties here
│  │  │  ├─ validation_messages.properties
│  │  │  ├─ validation_messages_en.properties
│  │  │  ├─ validation_messages_ja.properties
│  │  │  └─ validation_messages_ko.properties
│  │  └─ ...
│  └─ ...
├─ foo-auth
├─ ...
├─ foo-gateway
│
├─ foo-resource
│  ├─ ...
│  ├─ foo-application << module to register Bean for MessageSource here
│  └─ ...
│
├─ foo-library
│  ├─ ...
│  └─ util-lib << annotation for validation here
│ 
└─ foo-web
   └─ ...
spring.messages.basename=messages/messages, messages/validation_messages # <- how to fix?
spring.messages.encoding=UTF-8
# spring.resources.static-locations=file:../../foo-config/resources #not used anymore
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

@Documented
@Constraint(validatedBy = {})
@Target({ FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Pattern(regexp = IpV4.PATTERN, message = IpV4.NOT_MATCH)
@NotEmpty(message = IpV4.NOT_EMPTY)
public @interface IpV4 {

    String  PATTERN     = "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
    String  NOT_EMPTY   = "validation.ip-v4.not_empty";
    String  NOT_MATCH   = "validation.ip-v4.not_match";

    String message() default "invalid ip format";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> validationExceptionCaught(
        MethodArgumentNotValidException e) {
    return Collections.singletonMap("message", messageSource.getMessage(
            e.getBindingResult().getFieldError().getDefaultMessage()));
}

/**
 * for debugging
 */
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> validationExceptionCaught(
        MethodArgumentTypeMismatchException e) {
    return Collections.singletonMap("message", e.getMessage());
}

@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> validationExceptionCaught(
        ConstraintViolationException e) {
    return Collections.singletonMap("message",
            messageSource.getMessage(
                    e.getConstraintViolations().stream().findFirst()
                            .map(ConstraintViolation::getMessageTemplate)
                            .orElse(e.getMessage())));
}
import foo.bar.lib.common.validator.IpV4;
import lombok.Data;

@Data
public class BarRequest {
    @IpV4
    private String startIp;
    @IpV4
    private String endIp;
    private String description;
}

How can I use external files as resources for MessageSource To share verification messages?

I am afraid that my English skill is not good enough to communicate.

Upvotes: 0

Views: 4151

Answers (1)

Jungmin Lee
Jungmin Lee

Reputation: 41

In the end, I solved this problem.

The problem was that the configuration file was twisted or there was a typo in the file path while trying various methods.

I removed all properties related to MessageSource from the application.properties file and registered MessageSource bean in @Configuration class as the following source.

@Bean
public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasenames("classpath:messages/messages",
            "file:D:/foo-cloud/foo-config/resources/messages_validation");
    messageSource.setCacheSeconds(600);
    messageSource.setUseCodeAsDefaultMessage(true);
    messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
    return messageSource;
}

Thanks to M.Deinum for helping me.

Upvotes: 2

Related Questions