Search code examples
javaspringlocalizationbean-validationhibernate-validator

Named parameters in spring/hibernate JSR-303 (Bean validation) localized messages


I have a simple bean defining user credentials on signup registration form model:

public class UserCreds {
    @Length(min=1, max=50)
    @NotEmpty
    private String username;

    @Email
    private String email;

    @NotEmpty
    @Length(min=1, max=50)
    private String password;

    private String passwordConfirm;

    ... only obvious getters and setters below this line ...
}

I pass it's instance under "userCreds" model attribute, so i have a following localization bundle:

NotEmpty.userCreds.username=Username is required
NotEmpty.userCreds.password=Passsword is required
Length.userCreds.username=Username length must be between {2} and {1} symbols
Length.userCreds.password=Password length must be between {2} and {1} symbols
Email.userCreds.email=Ill-formed e-mail address

And it works!

The problem here: i don't want use this cryptic {N} placeholders, which may change their places occasionally (in bundle above 'max' is binded to {1} and 'min' is binded to {2}, which is at least non-obvious positioning) i wish to use full names of annotaions, as in

Length.userCreds.username=Username length must be between {min} and {max} symbols

(which is not working - when i try it - am getting java.lang.IllegalArgumentException: can't parse argument number min exception)

As i could when define message in code (the following example also works):

@Length(min=1, max=50, message = "Username length must be between {min} and {max} symbols")
@NotEmpty
private String username;

But when i define it in code i loose my ability to localize the message for different languages.

What do?

UPDATE 1

After a few hours of poking around debugger in my IDE i found that using the following class:

public class BakaMessageSource extends ReloadableResourceBundleMessageSource {

    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        /* original implementation *
        for (String basename : basenames) {
            List<String> filenames = calculateAllFilenames(basename, locale);
            for (String filename : filenames) {
                PropertiesHolder propHolder = getProperties(filename);
                MessageFormat result = propHolder.getMessageFormat(code, locale);
                if (result != null) {
                    return result;
                }
            }
        }
        */
        return null;
    }
}

With the following spring-servlet.xml declaration:

<bean id="messageSource" class="org.eientei.radiorandom.framework.crutchy.BakaMessageSource"
          p:defaultEncoding="UTF-8"
          p:alwaysUseMessageFormat="false"
            >
        <property name="basenames">
            <list>
                <!-- my application-specific localization bundles -->
                <value>classpath:messages/root/errorMessages</value>
                <value>classpath:messages/root/langMessages</value>
                <value>classpath:messages/user/signupMessages</value>
            </list>
        </property>
    </bean>

instead of original ReloadableResourceBundleMessageSource actually helps to get localized messages with named parameters.

But i guess swapping classes around at that point is not a proper solution. Which one would be?

UPDATE 2

I am such an idiot. I forgot in-code message and thought it got interpolated properly. Geez. So, alphabetical ordering... guess i can't ask for more.


Solution

  • I think that the actual order is alphabetical ("max" is lesser than "min") starting from Spring 3.0.4.

    From SPR-6730 discussion (Juergen Hoeller post)

    For the first option, Spring-managed field error arguments include the actual constraint annotation attributes in alphabetical order now. So for @Size(min=1,max=5), it'll include the field name as {0} , the max value as {1} , and the min value as {2}

    See https://jira.springsource.org/browse/SPR-6730