My app is fully-configured spring-boot app with thymeleaf templates engine. I18n configured as well so I can use it within my templates. Here is the config I use:
spring.messages.basename=i18n/messages
While manual validating fields I18n also work fine:
BindingResult result;
result.rejectValue("field", "some.i18n.code");
But once I want to implement some custom ConstraintValidator objects and use message field - no I18n involved, I receive plain codes as a response instead of a message. I.e.
{some.i18n.code}
I tried this solution - no result.
This on as well - same result.
What am I missing?
I guess I found the solution, maybe it will be helpful to others. All you have to do is to add the following definitions into your WebMvcConfigurerAdapter configuration implementation:
#Autowired
private MessageSource messageSource;
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setValidationMessageSource(messageSource);
return validatorFactoryBean;
}
#Override
public Validator getValidator() {
return validator();
}
An alternative solution is just declare this bean in any of your #Configuration classes:
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource);
return bean;
}
Due to declaring this, now my custom error codes from my custom validators are being searched for in my messages.properties (that I also have in a i18n subdirectory by declaring spring.messages.basename=i18n/messages).
Example validator code:
public class ContestValidator implements ConstraintValidator<ValidContest, CreateContestParameters> {
#Override
public void initialize(ValidContest constraintAnnotation) {
}
#Override
public boolean isValid(CreateContestParameters contestParameters, ConstraintValidatorContext context) {
boolean result = true;
if (!endDateIsEqualOrAfterStartDate(contestParameters)) {
context.buildConstraintViolationWithTemplate("{Contest.endDate.invalid}")
.addPropertyNode("endDate").addConstraintViolation();
result = false;
}
if (!registrationDeadlineIsBeforeStartDate(contestParameters)) {
context.buildConstraintViolationWithTemplate("{Contest.registrationDeadline.invalid}")
.addPropertyNode("registrationDeadline").addConstraintViolation();
}
return result;
}
}
Related
How can I i18n a spring #RestController property on javax.validation constraints?
I thought I could just add /src/main/resources/messages.properties and messages_de.properties, and then spring would detect them and enable proper i18n? But that seems not to be the case:
#RestController
public void TestController {
#PostMapping
public void post(#Valid #RequestBody Person p) {
}
}
public class Person {
private String firstname;
#javax.validation.constraints.NotBlank(message = "{errors.person.lastname}")
private String lastname;
}
messages.properties:
errors.person.lastname=person should provide a lastname
messages_de.properties:
errors.person.lastname=Person ohne Nachnamen
Problem: if I now send a POST request:
{
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"message": "'lastname': {errors.person.lastname}"
}
Question 1: do I really have to tell spring explicit to apply i18n as follows? Or can I somehow rely on auto-detect features?
Next step was adding the following configuration. Now the default message is resolved properly. But is it necessary to really add this whenever I want to have validation i18n?
#Configuration
public class MessageSourceConfig {
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource bundle = new ResourceBundleMessageSource();
bundle.setBasenames("messages");
return bundle;
}
}
Question 2: I those beans are required: how can I now send a post request to switch the language?
I tried adding get-query parameters like ?local=de, locale=de, ?lang=de, language=de, but none of them worked...
With the help of #M. Deinum above, this is the missing peace:
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource);
return bean;
}
Then having to send a web request with http header Accept-Language=de.
when I look at the implementation of RibbonConfig in demos around the web, I notice they always use the IClientConfig config parameter, but they never actually use it. For example:
#Autowired
IClientConfig ribbonClientConfig;
#Bean
public IRule ribbonRule(IClientConfig config) { // This parameter is never used
return new AvailabilityFilteringRule();
}
#Bean
public IPing ribbonPing(IClientConfig config) {
return new DummyPing();
}
Do you guys know what the IClientConfig config parameter used for? It wasn't used within the method itself in this case.
I found a very similar example in Moises Macero's book: Learn Microservices with Spring Boot, A Practical Approach to RESTful... e.g:
public class RibbonConfiguration {
#Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl(false, "/health");
}
#Bean
public IRule ribbonRule(IClientConfig config) {
return new AvailabilityFilteringRule();
}}
The scope of this configuration is changing the default Ribbon load balancing strategy.While
Moreover in the official documentation I found this:
#Configuration
class DefaultRibbonConfig {
#Bean
public IRule ribbonRule() {
return new BestAvailableRule();
}
#Bean
public IPing ribbonPing() {
return new PingUrl();
}
#Bean
public ServerList<Server> ribbonServerList(IClientConfig config) {
return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config);
}
#Bean
public ServerListSubsetFilter serverListFilter() {
ServerListSubsetFilter filter = new ServerListSubsetFilter();
return filter;
}
}
As you can see, the first two methods are without IClientConfig parameter, here official docs:
Customizing the Default for All Ribbon Clients
So I came back to my config file and I removed IClientConfig parmeter and the program still works.
In my opinion IClientConfig is useless in this moment.
But you can refer to IClientConfig author's comment: IClientConfig
Defines the client configuration used by various APIs to initialize clients or load balancers
and for method execution.
My project has a dependency on another one, and imports beans from it (using #ImportResource("foo.xml")).
foo.xml defines two datasources (datasource1 and datasource2), I would like to make datasource1 a primary (so all auto-configurations of Spring Boot will work).
Is it possible? I found out that there is a DefaultListableBeanFactory that has determinePrimaryCandidate method.
So the idea is to create my own ListableBeanFactory, that would extend the DefaultListableBeanFactory, but how to force Spring Boot to use my implementation?
Or maybe there is another, easier way to mark a given bean as primary (without changing the configuration where it is defined).
You can create a configuration in your project, which builds a new data source annotated as #Primary bean. This new data source will be the datasource1, which will be injected by spring to the new data source factory method. Here you have the working example.
The config:
#SpringBootApplication
public class BeanSpringExampleApplication
{
#Bean(name = "dataSource1")
public FakeDataSource dataSource1()
{
return new FakeDataSource("dataSource1");
}
#Bean(name = "dataSource2")
public FakeDataSource dataSource2()
{
return new FakeDataSource("dataSource2");
}
#Bean
#Primary
public FakeDataSource primaryDataSource(
#Qualifier("dataSource1") FakeDataSource dataSource1)
{
return dataSource1;
}
}
Here you see three beans (using FakeDataSource class), which simulate your situation. The primaryDataSource bean factory method simply returns the dataSource1 (it's just a mere data source selector).
The FakeDataSource is just a placeholder, to make example runnable:
public class FakeDataSource
{
private final String fakeProperty;
public FakeDataSource(String id)
{
fakeProperty = id;
}
/**
* #return the fakeProperty
*/
public String getFakeProperty()
{
return fakeProperty;
}
}
Finally, a test which proves everything is working:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BeanSpringExampleApplicationTests
{
#Autowired
private FakeDataSource fakeDataSource;
#Test
public void should_AutowirePrimaryDataSource() throws Exception
{
assertEquals("dataSource1", fakeDataSource.getFakeProperty());
}
}
I'm using Spring Boot 1.3.5 with Rest Controllers and everything is working fine.
I am also using Spring's validation sample techniques from the official documentation (JSR-303 Bean Validation API and Spring's validator interface, i tried both and faced the same problem) and the validations are working, but I am not able to configure custom messages.
I have configured a messages.properties file, and I can access the messages on this file just fine. However this validation seems not to be capable of reading or accessing my messages source (messages.properties) configured automatically via spring boot.
I can access the messages directly from the messages source object injected in controller via #Autowired (there's a comment in the code). However, the binding result of the Spring's validator interface or the JSR-303 Bean Validation seems to not be capable of accessing the messages.properties loaded in MessageSource. The result I have is that my errors have codes but don't have default messages.
Here is my Application class:
#SpringBootApplication
#ImportResource({ "classpath:security/cas-context.xml", "classpath:security/cas-integration.xml",
"classpath:security/security.xml" })
#EnableAutoConfiguration(exclude = VelocityAutoConfiguration.class) // http://stackoverflow.com/questions/32067759/spring-boot-starter-cache-velocity-is-missing
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean cxfServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/services/*");
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public Nfse nfseService() {
return new NfseImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), nfseService());
endpoint.publish("/nfseSOAP");
return endpoint;
}
}
Here is my Bean:
public class Protocolo {
private Long id;
#NotNull
#Min(1)
#Max(1)
private String protocolo;
private StatusProtocoloEnum status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProtocolo() {
return protocolo;
}
public void setProtocolo(String protocolo) {
this.protocolo = protocolo;
}
public StatusProtocoloEnum getStatus() {
return status;
}
public void setStatus(StatusProtocoloEnum status) {
this.status = status;
}
}
Here is My rest controller:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getAuthorities());
System.out.println(messageSource.getMessage("protocolo.tamanho", null, null));
// IN THIS PART I'M ABLE TO PRINT THE MESSAGE IF VALIDATION IS DISABLED
System.out.println(p.getProtocolo());
}
}
So, this code works fine and the method is not called since i'm calling the method with a invalid Protocolo. However, my angularJS client receives the response with the errors codes populated but with all the default messages empty since the validation is not seeing my loaded messages.properties.
Is there a way to make my Spring validation Interfaces or JSR-303 validation incorporate the loaded message.properties (messagesource) in spring boot ? How can i correct this ? If it's necessary i can paste my code sample of Spring Validation interfaces also.
Thank's a lot,
Tarcísio.
TEST CODE:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
System.out.println(messageSource.getMessage("Min.protocolo.protocolo", null, null));
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
System.out.println(bindingResult.getFieldError().getCode());
}
System.out.println(p.getProtocolo());
}
}
Edit:
Known Bug in Spring Boot 1.5.3 see https://github.com/spring-projects/spring-boot/issues/8979
In Spring Boot since 1.5.3 you need to do this
#Configuration
public class ValidationMessageConfig {
#Bean
public LocalValidatorFactoryBean mvcValidator(MessageSource messageSource) {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
and then it will work.
With version 1.5.2 and before you can extend WebMVcConfigurerAdapter
#Configuration
public class ProfileMvcConfig extends WebMvcConfigurerAdapter {
private MessageSource messageSource;
#Autowired
public ProfileMvcConfig(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* This method is overridden due to use the {#link MessageSource message source} in bean validation.
*
* #return A Validator using the {#link MessageSource message source} in bean validation.
*/
#Override
public Validator getValidator() {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
also see the documentation
In Spring Boot applicaton MessageSource is configured with a MessageSourceAutoConfiguration and you don't need to autowire it. For jsr303, create proper key-value pair in the messages.properties file. For "protocolo" field, you should have following values in property file.
NotNull.protocolo.protocolo=Field cannot be left blank
Min.protocolo.protocolo=Minimum value must be {1}
You can also check messages from property file like below in your code.
public void testar(#Valid #RequestBody Protocolo p,BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
}
}
you should have following values in property file:
Min.protocolo.protocolo=Minimum value must be {1}
then in the controller you obtain the message by calling function getMessage from messageSource object
Test code:
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
bindingResult.getFieldErrors().forEach(fieldError ->
System.out.println(messageSource.getMessage(fieldError, Locale.getDefault()))
);
}
System.out.println(p.getProtocolo());
}
I solved this in custom message in Spring validation read the last part of my answer.
Check this example as well.
I used a custom validator with custom annotation. I needed to change code in my custom validator.
public class PersonValidator implements ConstraintValidator {
#Override
public boolean isValid(final Person person, final ConstraintValidatorContext context) {
if (somethingIsInvalid()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Something is invalid.").addConstraintViolation();
return false;
}
return true;
}
}
I'm using the Validator API to validate my Dropwizard resources.
At this point I need to create a custom ConstraintValidator -- but my validator must be able to connect to a database.
In order to handle this, I am using #Autowire inside my custom ConstraintValidator to provide my configured beans (the database).
public class CustomValidator implements ConstraintValidator<CustomAnnotation, String> {
#Autowired private ApplicationContext applicationContext;
#Override
public void initialize(final CustomAnnotation customAnnotation) {
}
#Override
public boolean isValid(final Optional<String> value, final ConstraintValidatorContext context) {
applicationContext.getApplicationName();
return true;
}
}
In order to get autowiring to work with the validator, I believe I have to use 'LocalValidatorFactoryBean'.
I've configured mine as such:-
final LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setApplicationContext(applicationContext); // preconfigured with my beans
factory.afterPropertiesSet(); // seems to perform the setup of the ConstraintValidatorFactory with applicationContext.getAutowireCapableBeanFactory()
Dropwizard comes with a validator pre-configured in the Environment object.
So, to ensure that Dropwizard uses the Spring configured validator with injection, I've set the environment with the LocalValidatorFactoryBean like so:-
environment.setValidator(factory);
However, rather frustratingly my #Autowired private ApplicationContext applicationContext from my CustomValidator still appears to be null.
Does anyone know where I'm going wrong here or if there is an easier way?
Edit A bit more info on how it was done.
Set this up in my application:-
final ValidatorFactory constraintValidatorFactory = // custom validator factory
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
jerseyEnvironment.register(new ValidationConfigurationContextResolver(constraintValidatorFactory, factory));
Then used this class to replace the desired validator:-
public class ValidationConfigurationContextResolver implements ContextResolver<ValidationConfig> {
private final ConstraintValidatorFactory constraintValidatorFactory;
private final ValidatorFactory factory;
public ValidationConfigurationContextResolver(final ConstraintValidatorFactory constraintValidatorFactory,final ValidatorFactory factory) {
this.constraintValidatorFactory = constraintValidatorFactory;
this.factory = factory;
}
#Override
public ValidationConfig getContext(final Class<?> type) {
final ValidationConfig config = new ValidationConfig();
config.messageInterpolator(factory.getMessageInterpolator());
config.constraintValidatorFactory(constraintValidatorFactory); // custom constraint validator factory
config.parameterNameProvider(factory.getParameterNameProvider());
config.traversableResolver(factory.getTraversableResolver());
return config;
}
}