Bean Validation's resourcebundle parameterization possibility in JSF 2?

bertie picture bertie · Mar 21, 2011 · Viewed 11.9k times · Source

Using the resourcebundle with BV in JSF 2 would look something like this :

public class UserBean {
    @Size(min=13, message="{creditcard.length}")
    public String getCreditCard() {
        return this.creditCard;
    }
}

And i have to define the ResourceBundle entry in one of the properties file that can be registered in the faces-config.xml

creditcard.length=credit card length must be at least 13 characters

We can see that the value of the creditcard.length is non parameterized.

Can I do parameterized ResourceBundle entry that can be populated from BV or perhaps somewhere else?


This is a simple scenario that I would like to achieve :

creditcard.length=credit card length must be at least {0} characters. thank you for choosing the {1} credit card.

And I was hoping for something like this :

public class UserBean {
    @Size(
        min=13, 
        message="{creditcard.length}", 
        messageParams={"13", "plantvszombie"})
    public String getCreditCard() {
        return this.creditCard;
    }
}

And the error message for the creditcard property will display a string like this when the validation fails :

credit card length must be at least 13 characters. thank you for choosing the plantvszombie credit card.


Is this ResourceBundle message parameterization possible?

Please share your experience on this matter.

Thank you !

Answer

victor herrera picture victor herrera · Mar 28, 2011

Maybe you already know that messages for Bean Validation are defined in the Resource Bundle ValidationMessages.properties in the root of your classes (i.e WEB-INF\classes\ValidationMessages.properties).

These messages can have parameters but they don't work as in JSF. There is a interface called MessageInterpolator that transform the message pattern into the actual message.

Default interpolator works with named parameters like in the message: Value must be between {min} and {max}. The values between { and } are resolved first in resource bundle of the application; later in the resource bundle of the provider, and last in properties of the constraint annotation. (This is more or less how it works, the complete algorithm is in section 4.3 of the Bean Validation specification).

Suppose you define the attribute message of the Size annotation as {creditCard.message}

The content of ValidationMessage.properties could be

creditCard.message=Credit card length must be at least {min} characters. \
                   Thank you for choosing the plantsvszombies credit card.

You could replace plantsvszombies with a property:

creditCard.message=Credit card length must be at least {min} characters. \
                   Thank you for choosing the {creditCard.type} credit card.
creditCard.type=plantsvszombies

You could even use two parameters in the message of the constraint

Size(min=13, message="{creditCard.message} {plantsvszombies.messages}")

and define the resource bundle as

creditCard.message=Credit card length must be at least {min} characters.
plantsvszombies.message=Thank you for choosing the plantsvszombies credit card.

I think this is a simple and clean approach.


But if you want something more like defining custom parameters in the declaration of the constraint you could use a custom message interpolator. Please notice that this could be a trickier solution.

Well, you could define a syntax to introduce your parameters in the message string. Then, let the default interpolator resolve the message. The syntax for custom parameters won't be understood by the default interpolator and they will still be there after resolving. Then, the custom interpolator can replace replace the custom parameters.

This is more easy to understand with an example.

First, a message is defined like {creditCard.message}[plantsvszombies]. For this syntax, the content between the square brackets are the indexed parameters separated by commas (here is only one parameter).

Next, the content of the resource bundle is defined with:

 creditCard.message=Credit card length must be at least {min} characters. \
                    Thank you for choosing the {0} credit card.

When the default interpolator replaces the first part of the expression, we'll have:

Credit card length must be at least 13 characters. \ Thank you for choosing the {0} credit card.[plantsvszombies]

Then, the custom interpolator will take the last expression and split the content to get the tokens and replace the indexed parameters with the token in the corresponding index (parameter[0]=plantsvzzombies).

So the message will be:

Credit card length must be at least 13 characters. \ Thank you for choosing the plantsvszombies credit card.

This is the code of the custom interpolator for this syntax (not optimized and the regex pattern could not work if there are other square brackets in the first expression or in the tokens).

 package validation;

 import java.util.Locale;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.validation.MessageInterpolator;
 import javax.validation.Validation;

 public class MyInterpolator implements MessageInterpolator{
    private MessageInterpolator interpolator;

    public MyInterpolator() {
        //we need to delegate to the default interpolator
        this.interpolator = Validation.byDefaultProvider().configure().getDefaultMessageInterpolator();
    }

    private static final Pattern parametersPattern=Pattern.compile("\\[(.+)\\]$");

    protected static String replaceParameters(String message){
        Matcher matcher = parametersPattern.matcher(message);
        String values[]={};
        if(matcher.find()){
            values=matcher.group(1).split("\\s*,\\s*");
            message=message.substring(0, matcher.start());
            for(int i=0; i < values.length; i++){
                message=message.replace("{"+i+"}", values[i]);
            }
        }
        return message;
    }

    @Override
    public String interpolate(String messageTemplate, Context context) {
        String message = interpolator.interpolate(messageTemplate, context);
        return replaceParameters(message);
    }

    @Override
    public String interpolate(String messageTemplate, Context context, Locale locale) {
        String message = interpolator.interpolate(messageTemplate, context);
        return replaceParameters(message);
    }

}

The registering of the interpolator goes in the xml file called META-INF/validation.xml (4.4.6 of the spec).

<?xml version="1.0" encoding="UTF-8"?>
<validation-config
xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.0.xsd">
    <message-interpolator>validation.MyInterpolator</message-interpolator>
</validation-config>  

This is a bit complicated solution because the constraint annotations doesn't accept parameters for the messages and because in the interpolator, we can not get many information of the property that is being validated. If I find an easier solution, I will post it.