[hibernate-dev] [Bean Validation] MessageInterpolator improved algorithm

Emmanuel Bernard emmanuel at hibernate.org
Sun Jan 25 15:58:52 EST 2009


In the previous algorithm, parameter values were themselves  
interpolated which was undesirable and could lead to security issue.
I have reworked the algorithm to resolve the custom ResourceBundle in  
priority, the built-in bundle as a back up and finally regular  
parameters.

Check it out

4.3. Message interpolation

4.3.1. Default message interpolation

A conforming implementation includes a default message interpolator.  
This message interpolator shall use the algorithm defined here to  
interpolate message descriptors into human-readable messages.

Each constraint defines a message descriptor via its message property.  
Every constraint definition shall define a default message descriptor  
for that constraint. Messages can be overridden at declaration time in  
constraints by setting the message property on the constraint.

The message descriptor is a string literal and may contain one or more  
message parameters. Message parameters are string literals enclosed in  
braces.

Example 4.1. Message using parameters

Value must be between {min} and {max}
4.3.1.1. Algorithm

The default message interpolator uses the following steps:

Message parameters are extracted from the message string and used as  
keys to search the ResourceBundle named ValidationMessages (often  
materialized as the property file/ValidationMessages.properties and  
its locale variations) using the defined locale (see below). If a  
property is found, the message parameter is replaced with the property  
value in the message string. Step 1 is applied recursively until no  
replacement is performed (ie. a message parameter value can itself  
contain a message parameter).

Message parameters are extracted from the message string and used as  
keys to search the Bean Validation provider's built-in ResourceBundle  
using the defined locale (see below). If a property is found, the  
message parameter is replaced with the property value in the message  
string. Contrary to step 1, step 2 is not processed recursively.

If step 2 triggers a replacement, then step 1 is applied again.  
Otherwise step 4 is performed.

Message parameters are extracted from the message string. Those  
matching the name of an attribute of the constraint declaration are  
replaced by the value of that attribute.

The defined locale is as followed:

if the locale is passed to the interpolator method interpolate(String,  
CosntraintDescriptor, Object, Locale), this Locale instance is used.

otherwise, the default Locale as provided by Locale.getDefault() is  
used.

The proposed algorithm ensures that custom resource bundle always have  
priority over built-in resource bundle at all level of the recursive  
resolution. It also ensures that constraint declarations attributes  
values are not expanded further.

4.3.2. Custom message interpolation

A custom message interpolator may be provided (e.g., to interpolate  
contextual data, or to adjust the default Locale used). A message  
interpolator implements the MessageInterpolator interface.

/**
  * Interpolate a given constraint violation message.
  *
  * @author Emmanuel Bernard
  * @author Hardy Ferentschik
  */
public interface MessageInterpolator {
	/**
	 * Interpolate the message from the constraint parameters and the  
actual validated object.
	 * The locale is defaulted according to the  
<code>MessageInterpolator</code> implementation
	 * See the implementation documentation for more detail.
	 *
	 * @param message The message to interpolate.
	 * @param constraintDescriptor The constraint descriptor.
	 * @param value The object being validated
	 *
	 * @return Interpolated error message.
	 */
	String interpolate(String message,
					   ConstraintDescriptor constraintDescriptor,
					   Object value);

	/**
	 * Interpolate the message from the constraint parameters and the  
actual validated object.
	 * The Locale used is provided as a parameter
	 *
	 * @param message The message to interpolate.
	 * @param constraintDescriptor The constraint descriptor.
	 * @param value The object being validated
	 * @param locale the locale targeted for the message
	 *
	 * @return Interpolated error message.
	 */
	String interpolate(String message,
					   ConstraintDescriptor constraintDescriptor,
					   Object value,
					   Locale locale);
}
message is the message descriptor as seen in  
@ConstraintAnnotation.message or provided to the ConstraintContext  
methods.

constraintDescriptor is the ConstraintDescriptor object representing  
the metadata of the failing constraint (see Constraint metadata  
request API).

value is the value being validated.

MessageInterpolator.interpolate(String, ConstraintDescriptor, Object)  
is invoked by for each constraint violation report generated. The  
default Locale is implementation specific.

MessageInterpolator.interpolate(String, ConstraintDescriptor, Object,  
Locale) can be invoked by a wrapping MessageInterpolator to enforce a  
specific Locale value by bypassing or overriding the default Locale  
strategy.

A message interpolator implementation shall be threadsafe.

The message interpolator is provided to the ValidatorFactory at  
construction time using  
ValidatorFactoryBuilder.messageInterpolator(MessageInterpolator). This  
message interpolator is shared by all validators generated by this  
ValidatorFactory.

It is possible to override the MessageInterpolator implementation for  
a given Validator instance by  
invokingValidatorFactory 
.defineValidatorState 
().messageInterpolator(messageInterpolator).getValidator().

It is recommended that MessageInterpolator implementations delegate  
final interpolation to the Bean Validation default MessageInterpolator  
to ensure standard Bean Validation interpolation rules are followed,  
The default implementation is accessible through  
ValidatorFactoryBuilder.getDefaultMessageInterpolator().

4.3.3. Examples

These examples describe message interpolation based on the default  
message interpolator's built-in messages (see Appendix B, Standard  
ResourceBundle messages), and the ValidationMessages.propertiesfile  
shown in table Table 4.2, “message interpolation”. The current locale  
is assumed English.

//ValidationMessages.properties
myapp.creditcard.error=credit card number not valid

Table 4.2. message interpolation

Failing constraint declaration	interpolated message
@NotNull	must not be null
@Max(30)	must be less than or equal to 30
@Size(min=5, max=15, message="Key must have between {min} and {max}  
characters")	Key must have between 5 and 15 characters
@Digits(integer=9, fraction=2)	numeric value out of bounds (<9  
digits>.<2 digits> expected)
@CreditCard(message={myapp.creditcard.error})	credit card number not  
valid
Here is an approach to specify the Locale value to choose on a given  
Validator. Locale aware MessageInterpolator. See Section 4.4,  
“Bootstrapping” for more details on the APIs.

Example 4.2. Use MessageInterpolator to use a specific Locale value

/**
  * delegates to a MessageInterpolator implementation but enforce a  
given Locale
  */
public class LocaleSpecificMessageInterpolator implements  
MessageInterpolator {
     private final MessageInterpolator defaultInterpolator;
     private final Locale defaultLocale;

     public LocaleSpecificMessageInterpolator(MessageInterpolator  
interpolator, Locale locale) {
         this.defaultLocale = locale;
         this.defaultInterpolator = interpolator;
     }

     /**
      * enforece the locale passed to the interpolator
      */
     public String interpolate(String message,
                               ConstraintDescriptor  
constraintDescriptor,
                               Object value) {
         return defaultInterpolator.interpolate(message,  
constraintDescriptor,
                                            value, this.defaultLocale);
     }

     // no real use, implemented for completeness
     public String interpolate(String message,
                               ConstraintDescriptor  
constraintDescriptor,
                               Object value,
                               Locale locale) {
         return defaultInterpolator.interpolate(message,  
constraintDescriptor, value, locale);
     }
}


Locale locale = getMyCurrentLocale();
MessageInterpolator interpolator = new  
LocaleSpecificMessageInterpolator(
                                         
validatorFactory.getMessageInterpolator(),
                                        locale);

Validator validator = validatorFactory.defineValidatorState()
                                       .messageInterpolator 
(interpolator)
                                       .getValidator();
Most of the time, however, the relevant Locale will be provided by  
your application framework transparently. This framework will  
implement its own version of MessageInterpolator and pass it during  
theValidatorFactory configuration. The application will not have to  
set the Locale itself. This example shows how a container framework  
would implement MessageInterpolator to provide a user specific default  
locale.

Example 4.3. Contextual container possible MessageInterpolator  
implementation

public class ContextualMessageInterpolator {
     private final MessageInterpolator delegate;

     public ContextualMessageInterpolator(MessageInterpolator  
delegate) {
         this.delegate = delegate;
     }

     public String interpolate(String message, ConstraintDescriptor  
constraintDescriptor,
                               Object value) {
         Locale locale = Container.getManager().getUserLocale();
         return this.delegate.interpolate(
                         message, constraintDescriptor, value, locale );
     }

     public String interpolate(String message, ConstraintDescriptor  
constraintDescriptor,
                               Object value, Locale locale) {
         return this.delegate.interpolate(message,  
constraintDescriptor, value, locale);
     }
}


//Build the ValidatorFactory
ValidatorFactoryBuilder builder = Validation.getBuilder();
ValidatorFactory factory = builder
     .messageInterpolator( new  
ContextualMessageInterpolator 
( builder.getDefaultMessageInterpolator() ) )
     .build();

//The container uses the factory to validate constraints using the  
specific MessageInterpolator
Validator validator = factory.getValidator();
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/hibernate-dev/attachments/20090125/5df74021/attachment.html 


More information about the hibernate-dev mailing list