Conflict between ValidationMessages.properties files
Asked Answered
E

2

5

I use to collect all my validation constraints in a common library. In the root of the jar I put a ValidationMessages_it.properties file. All works fine, if I put this library into a jsf 2 war project all validation messages are shown correctly. However a problem arise if I put another ValidationMessages_it.properties in the war artifact too. In this case a {library.message_key} string is shown.

I think Bean Validation find the right property file in the war and does not take into account that in the library. How can I solve?

Example

I have a library, commons.jar, that contains custom constraints. In order to set messages for these constraints I've added a ValidationMessages_it.properties in the root of this library

commons.jar
    |
    + library
    |   |
    |   + CustomConstraint.class
    |
    + ValidationMessages_it.properties

ValidationMessages_it.properties

library.custom=Questo è l'errore di cui parlavo

CustomConstraint.java

@Pattern( regexp = "[a-z]", message = "{library.custom}" )
@Constraint( validatedBy = {} )
@Documented
@Target( { ElementType.METHOD, ElementType.FIELD } )
@Retention( RetentionPolicy.RUNTIME )
public @interface CustomConstraint {
    String message() default "C'è un errore";

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

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

PS: note that the message key is on the @Pattern annotation instead of message(), this could seem a mistake but otherwise it never works!

After that I want to use this commons.jar in my web app project (jsf/mojarra 2.1). All works fine. The displayed error message is "Questo è l'errore di cui parlavo".

But now suppose I define new validation constraints in my webapp, so I want to supply translations for these constraints by adding a ValidationMessages_it.properties in WEB-INF/classes folder. In this case the displayed error message is "{library.custom}"

So I think that BV (or jsf?) find the bundle in the war and does not take into account that in the commons.jar. It does not find the key library.custom in the ValidationMessages_it.properties that resides in the WEB-INF/classes folder thus return {library.custom} literally.

Example 2

Based on Bean Validation constraints in a shared library, my package structure seems correct. I uploaded a simple web app to show the problem:

I tested the webapp in Glassfish 3.1.2, JBoss AS 7.1.1, Geronimo 3.0.0

Glassfish and JBoss have the same behavior. In Geronimo it works a little bit better.

Evansville answered 22/7, 2012 at 9:42 Comment(5)
What is it you actually want to achieve? Why do you want to add the bundle several times to your application?Masonry
Can you clarify your configuration? Where and how do you add another ValidationMessages_it.properties. Maybe you can outline the full structure of your war and of course why you want to add the same property file twice. In case you want to be able to specify multiple different properties file you should have a look at AggregateResourceBundleLocator (provided you are using Hibernate Validator)Going
@Hardy: it's not the same bundle, it is another one that contains other messages. See the edit on my postEvansville
@Gunnar, since you are in JSR 349 EG may you have a look at my edits? I don't know if it's an error in my configuration, a bug or an expected behavior.Evansville
The FileFactory links for the uploaded webapps are now invalid: "Invalid Download Link: This file may have been deleted, or it may have expired. This error can also occur if the file link is invalid."Absonant
G
3

I think the solution in your case is the mentioned AggregateResourceBundleLocator. However, you cannot have the same name for the property files. Internally ResourceBundle#getBundle is called which does return a single ResourceBundle. There is no concept of combining/merging properties files with the same name.

EDIT 1

Regarding a standard way of doing it - unfortunately there is none. There is an open issue for Bean Validation 1.1 (BVAL-252) to address the ability to provide constraint libraries, but there is nothing decided yet and the message interpolation would needs addressing as well. Maybe you have an idea on how it should work. If so provide your suggestion to the expert group. Check beanvalidation.org

Going answered 26/7, 2012 at 14:22 Comment(3)
I'm a little confused. It's BV that compel me to use the same name for the bundles. What I'm trying to do is a very common task so I thought EG has provided a standard way to accomplish it. Instead here you suggest to use an hibernate class. I've opened another question, see edit2. Thank you very much for your patienceEvansville
There is no "standard" way of doing what you want to do. There is an open issue for Bean Validation 1.1 (hibernate.onjira.com/browse/BVAL-252) to address the ability to provide constraint libraries, but there is nothing decided yet and the message interpolation would needs addressing as well. Maybe you have an idea on how it should work. If so provide your suggestion to the expert group. Check beanvalidation.orgGoing
Thank you very much Hardy. I thought it was a bug. If you update your answer I'll glad to accept it.Evansville
A
5

By default, if multiple JAR/WAR/etc. files contain a resource bundle file with the exact same name, only one of them will be used for validator messages. This is the issue here: both commons.jar and the application WAR contain a resource with name ValidationMessages_it.properties.

Hibernate Validator 5.2, released in July 2015, added support for aggregating all resource bundles with the same name from different JAR files. This functionality is disabled by default, and can be configured in org.hibernate.validator.resourceloading.PlatformResourceBundleLocator.

Example:

PlatformResourceBundleLocator resourceBundleLocator =
        new PlatformResourceBundleLocator(
                ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, 
                null, true);

Validator validator = Validation.byProvider(HibernateValidator)
    .configure()
    .messageInterpolator(
            new ResourceBundleMessageInterpolator(resourceBundleLocator))
    .buildValidatorFactory()
    .getValidator();

Equivalent code for a Spring project using Java config:

@Bean
public LocalValidatorFactoryBean validator() {
    PlatformResourceBundleLocator resourceBundleLocator =
            new PlatformResourceBundleLocator(
                    ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, 
                    null, true);

    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
    factoryBean.setMessageInterpolator(
            new ResourceBundleMessageInterpolator(resourceBundleLocator));
    return factoryBean;
}

For a Spring Boot 3.x project using Web MVC, the following configures both the default validator used by Spring, as well as the validator used by Web MVC for validating controller parameters and the like:

import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class ValidationConfig {
    /**
     * Matches the bean name from {@code ValidationAutoConfiguration.defaultValidator}.
     */
    @Bean
    public LocalValidatorFactoryBean defaultValidator() {
        PlatformResourceBundleLocator resourceBundleLocator =
                new PlatformResourceBundleLocator(
                        ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, 
                        null, true);

        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setMessageInterpolator(
                new ResourceBundleMessageInterpolator(resourceBundleLocator));
        return factoryBean;
    }

    @Bean
    public WebMvcConfigurer validatorConfigurer(Validator defaultValidator) {
        return new WebMvcConfigurer() {
            @Override
            public Validator getValidator() {
                return defaultValidator;
            }
        };
    }
}
Absonant answered 30/10, 2015 at 18:31 Comment(5)
This didn't work. I've tried your @Bean definition, but it collected only resources from single JAR. Hibernate Validator 6.1.5Amalamalbena
But I am not in spring boot, but in Spring. I guess that's the problemAmalamalbena
@BogdanMart This should work just as well in a Spring project as a Spring Boot project. Both JAR files are imported and both have a file named ValidationMessages.properties (or one of its locale-specific variants)?Absonant
Yes. But as I have noticed, PlatformResourceBundleLocator gets instantiated multiple times with that parameter passed in as false. Hibernate is instantiating it. My guess is that Spring is using different instance than one I've created. As we are using Validation of controller parameters, not manual validation, But we resorted to use single properties file located in same jar that our models, so it's good for now. Perhaps would come back to this issue later.Amalamalbena
@BogdanMart I've recently upgraded my app to Spring Boot 3.1.1. I've updated my answer to include my Boot config, which includes additional special configuration used by Web MVC. Maybe this is what you need.Absonant
G
3

I think the solution in your case is the mentioned AggregateResourceBundleLocator. However, you cannot have the same name for the property files. Internally ResourceBundle#getBundle is called which does return a single ResourceBundle. There is no concept of combining/merging properties files with the same name.

EDIT 1

Regarding a standard way of doing it - unfortunately there is none. There is an open issue for Bean Validation 1.1 (BVAL-252) to address the ability to provide constraint libraries, but there is nothing decided yet and the message interpolation would needs addressing as well. Maybe you have an idea on how it should work. If so provide your suggestion to the expert group. Check beanvalidation.org

Going answered 26/7, 2012 at 14:22 Comment(3)
I'm a little confused. It's BV that compel me to use the same name for the bundles. What I'm trying to do is a very common task so I thought EG has provided a standard way to accomplish it. Instead here you suggest to use an hibernate class. I've opened another question, see edit2. Thank you very much for your patienceEvansville
There is no "standard" way of doing what you want to do. There is an open issue for Bean Validation 1.1 (hibernate.onjira.com/browse/BVAL-252) to address the ability to provide constraint libraries, but there is nothing decided yet and the message interpolation would needs addressing as well. Maybe you have an idea on how it should work. If so provide your suggestion to the expert group. Check beanvalidation.orgGoing
Thank you very much Hardy. I thought it was a bug. If you update your answer I'll glad to accept it.Evansville

© 2022 - 2024 — McMap. All rights reserved.