I followed this link to implement a domain validation. However when I attempt to move the messages into a properties file, it doesn't work. I feel like it might be an issue with my validator implementation but can't quite figure out what is wrong. I followed a Baeldung article for this. The output doesn't get parsed and instead is displayed as "{modeltype.size.max}".
My domain object property looks like this:
#Column(name = "MODEL_TYPE")
#Size(max = 20, message = "{modeltype.size.max}")
private String modelType;
I also defined Beans for both the MessageSource & LocalValidatorFactoryBean like so:
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
My domain-specific validator method defined inside of the domain itself and called in the constructor:
private void validate(Equipment equipment) {
EntityValidator<Equipment> equipmentEntityValidator =
new AbstractEntityValidator<>(Validation
.buildDefaultValidatorFactory()
.getValidator());
Set<ConstraintViolation<Equipment>> failedValidations =
equipmentEntityValidator.validate(equipment);
List<String> failedValidationMessages = new ArrayList<>();
failedValidations.forEach(failedValidation -> failedValidationMessages.add(failedValidation.getMessage()));
if (!failedValidationMessages.isEmpty()) throw new ValidationException(String.join(",", failedValidationMessages));
}
This exception is then handled inside of the GlobalExceptionHandler and part of the response DTO are the constraint violations.
Related
I am trying to throw custom message while validating request parameter
#Size(max = 10, message = "{custom.message}")
#RequestParam(value = "param") String param)
My Controller method is annotated with #Validated .
Here is my message source configuration
#Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:messages/messages");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
return messageSource;
}
#Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean localValidator = new LocalValidatorFactoryBean();
localValidator.setValidationMessageSource(messageSource());
return localValidator;
}
Same annotation works(shows custom message) in DTO but when using for request paramter the message is
custom.message
Why is this happening? Thanks.
Got to bug, in message properties message name had a typo. In message properties i have custom_message but i was using custom.message.
I want to use localization to localize the Swagger Documentation. But I can only provide compile time constants to Annotations. So I'm confused how to provide read messages from messages_**.properties and provide it to annotations.
Message Source:
#Configuration
public class CustomMessageSourceConfig {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
#Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.ENGLISH);
return slr;
}
}
Reading messages from messages_**.properties:
#Component
public class MessagesByLocaleServiceImpl implements MessagesByLocaleService {
#Autowired
private MessageSource messageSource;
#Override
public String getMessage(String id) {
Locale locale = LocaleContextHolder.getLocale();
return StringEscapeUtils.unescapeJava(messageSource.getMessage(id, null, locale));
}
}
Here is how I'm reading messsages in Java Code:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.boot"))).build()
.apiInfo(apiInfo())
.tags(new Tag("Netmap Mode Service", messageSource.getMessage(MessageCodes.SWAGGER_WINDOWS_ONLY)));
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title(messageSource.getMessage(MessageCodes.SWAGGER_TITLE))
.description(messageSource.getMessage(MessageCodes.SWAGGER_DESCRIPTION))
.contact(messageSource.getMessage(MessageCodes.SWAGGER_CONTACT)).build();
}
But how can I provide these messages to Swagger Annotations.
#ApiOperation(value = "Add Netmap mode ", notes = "**I want to read properties here**")
#ApiImplicitParams({
#ApiImplicitParam(value = SwaggerSinglePoint.DESC_MODE_NAME, dataType = CDSwaggerPrimitives.STRING, name = SwaggerSinglePoint.MODE_NAME, paramType = CDSwaggerPrimitives.PARAMA_TYPE_QUERY),
#ApiImplicitParam(value = SwaggerSinglePoint.DESC_MODE_BUFFER_SIZE, dataType = CDSwaggerPrimitives.INETEGER, name = SwaggerSinglePoint.BUFFER, paramType = CDSwaggerPrimitives.PARAMA_TYPE_QUERY)})
#RequestMapping(method = RequestMethod.POST, produces = CDConstants.JSON_RESPONSE_DATA_FORMAT, consumes = CDConstants.JSON_REQUEST_DATA_FORMAT)
#SuppressWarnings({ "squid:S3776", "squid:S1319", "unused" })
public String testController(#RequestBody(required = false) HashMap requestParamMap, HttpServletResponse response,
HttpServletRequest request) {
I want to read messages in these annotations. Any guidance or suggestions would be highly appreciated.
It's always better to decouple your documentation comments from your code (reading text from external property file rather than ineserting as plain text)
Use the placeholder like so,instead of
#ApiOperation(value = "Add Netmap mode " ,...)
use
#ApiOperation(value = ${message.addNetMode} ,...)
Here inside "messages_**.properties" file there should be key-value pair
message.addNetMode=Add Netmap mode
Also register the property file in your configuration on class level
#PropertySource("classpath:messages_**.properties")
**Note that values for some annotations might not be supported.Refer docs http://springfox.github.io/springfox/docs/current/#support-for-documentation-from-property-file-lookup
You can get values from application.properties by using SPEL i.e. ${}:
#Annotation(value="${request.reminder.mails.cron.expression}")
Note:- the props name should be complete name from application.properties.
There is a messageSource Bean:
#Bean
public MessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource=new ReloadableResourceBundleMessageSource();
messageSource.setDefaultEncoding("UTF-8");
messageSource.setBasenames("classpath:/messages/messages");
return messageSource;
}
I have read somewhere that If I use ReloadableResourceBundleMessageSource messageSource shouldn't be cached and everytime look into the properties file if theere is particular string. Sadly with bean created that way It doesn't work. After compiling If I add some properties Spring Boot won't find it.
Properties file is in /resources/messages/messages.properties and /resources/messages/messages_en.properties .
Try this solution.
First: configure the bean in your web configuration as shown below.
#Bean
public MessageSource messageSource () {
ReloadableResourceBundleMessageSourceExt messageResource =
new ReloadableResourceBundleMessageSourceExt();
messageResource.setAlwaysUseMessageFormat(false);
messageResource.setBasenames("classpath:messages");
messageResource.setDefaultEncoding(CHARACTER_ENCODING);
messageResource.setFallbackToSystemLocale(true);
messageResource.setUseCodeAsDefaultMessage(false);
messageResource.setCacheSeconds(1); // by default it set to -1 which means cache
// forever messageSourse.
// set 0 to check reload messeageSource on
// every getMessageSource request but reload
// only those files which last modified
// timestamp is changed.
// value greater than 1 is treated as the
// time interval between reload.
return messageResource;
}
Second: create a class which extends ReloadableResourceBundleMessageSource to expose the protected method of its inner class as shown below.
public class ReloadableResourceBundleMessageSourceExt extends ReloadableResourceBundleMessageSource {
public Properties getPropertiesByFileName(String fileName){
return super.getProperties(fileName).getProperties();
}
}
Third: Now Autowired the bean like this.
#Service
public class MyMessagesBundleService {
final private String fileName = "classpath:messages";
#Autowired
ReloadableResourceBundleMessageSourceExt messageSource;
Properties properties = messageSource.getPropertiesByFileName(this.fileName);
// now change the properties and saved it.
// after saved call clear cache and get again.
messageSource.clearCache();
}
Hi have two different message bundles. How can I directly inject them into a spring bean MessageSource?
The following does not work:
#Resource(name = "${messages_one_EB.properties}")
private MessageSource messageSourceOne;
#Resource(name = "${messages_two_EN.properties}")
private MessageSource messageSourceTwo;
Result: java.lang.IllegalArgumentException: Could not resolve placeholder 'messages_one_EB.properties'
Probably it works similar, but how? I could not find any example in the docs.
Both bundles are placed under src/main/resources/
I guess you can declare 2 different beans for your message sources and then inject them by name:
#Bean
public MessageSource messageSource1() {
final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:i18n/messages1");
messageSource.setFallbackToSystemLocale(false);
messageSource.setCacheSeconds(0);
return messageSource;
}
#Bean
public MessageSource messageSource2() {
final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:i18n/messages2");
messageSource.setFallbackToSystemLocale(false);
messageSource.setCacheSeconds(0);
return messageSource;
}
Then in your class:
#Resource(name = "messageSource1")
private MessageSource messageSourceOne;
#Resource(name = "messageSource2")
private MessageSource messageSourceTwo;
I have the Enum:
public enum EmployeeErrorCode {
DELETE_SUCCESS,
//... Other enumerators
#Override
public String toString(){
ApplicationContext ctx = ContextLoader
.getCurrentWebApplicationContext();
MessageSource messageSource = (MessageSource) ctx
.getBean("messageSource"); //How to avoid this?
switch (this) {
case DELETE_SUCCESS:
return messageSource.getMessage("deleteEmployee.success",
null, LocaleContextHolder.getLocale());
//... Other cases
default:
return null;
}
}
}
In the toString nethod I specified the messages for any Enumerator, but I used getBean method to programmatically get the appropriate bean. How can I avoid that?
I tried to inject the bean via
#Autowired
MessageSource messageSource;
but it didn't work. In fact, messageSource was just null. Is there a way to do that corretly at all?
If MessageSource is a bean that opens a properties file, then for example if your properties file is called Messages.properties, then you can use
ResourceBundle bundle = ResourceBundle.getBundle("Messages", LocaleContextHolder.getLocale());
String message = bundle.getString("deleteEmployee.success");
EDIT: Another possible method is to inject the MessageSource into your enums (idea from my solution at Java Generics and Enum, loss of template parameters ), like so:
public enum EmployeeErrorCode {
DELETE_SUCCESS {
#Override
public String toString() {
return messageSource.getMessage("deleteEmployee.success", null, LocaleContextHolder.getLocale());
}
},
//... Other enumerators
private MessageSource messageSource;
static class EnumInitializer {
#Autowired
private MessageSource messageSource;
#PostConstruct
public void init() {
for(EmployeeErrorCode errorCode : EmployeeErrorCode.values() {
errorCode.messageSource = getMessageSource();
}
}
public MessageSource getMessageSource() {
return messageSource;
}
}
}
But I think the other one is a bit cleaner.