Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-6730

Access JSR-303 validation contraint attributes in localized messages

    Details

    • Type: Improvement
    • Status: Closed
    • Priority: Minor
    • Resolution: Complete
    • Affects Version/s: 3.0 GA
    • Fix Version/s: 3.0.4
    • Component/s: Core, Web
    • Labels:
      None

      Description

      Related to: SPR-6407 and SPR-5418.

      When using declarative validation constraints with Spring, custom/localized messages should be able to access the arguments to the validation annotation in a consistent manner that is guaranteed to work across validator providers.

      For example, when applying an annotation like "@Size(min=1, max=25)" to a field you should be able to provide an error message format like "Size.fieldName=Size of

      {0}

      must be between

      {1}

      and

      {2}

      ".

      Currently, the values of the arguments passed to the annotation are available to the message source in an arguments array, but the position of each value in the array is not consistent. It appears that the positions of the arguments in the array can change depending on the validator provider and potentially change between releases of a validator provider. If the framework does currently provide some guarantee of the order, then this should be documented.

      Here are some examples of validator annotations and the arguments array that is available to the message source when the validation fails. In all cases the first ([0]) element in the array is a DefaultMessageSourceResolvable containing the name of the field in error.

      @Size(min=1, max=25)

      index contents
      1 "message" argument from annotation
      2 "min" argument from annotation
      3 "payload" argument from annotation
      4 "max" attribute of annotation
      5 "groups" attribute of annotation

      @Max(25)

      index contents
      1 "value" argument from annotation
      2 "message" argument from annotation
      3 "groups" attribute of annotation
      4 "payload" argument from annotation

      @Min(1)

      index contents
      1 "value" argument from annotation
      2 "message" argument from annotation
      3 "groups" attribute of annotation
      4 "payload" argument from annotation

      @Pattern(regexp="[A-Z]+")

      index contents
      1 "message" argument from annotation
      2 "payload" argument from annotation
      3 "flags" argument from annotation
      4 "regexp" attribute of annotation
      5 "groups" attribute of annotation
      1. ConstraintsArgumentsMappings.java
        2 kB
        Krzysztof Witukiewicz
      2. SpringValidatorAdapterWithGroups.java
        3 kB
        Krzysztof Witukiewicz

        Issue Links

          Activity

          Hide
          stela Stefan Larsson added a comment -

          The randomization happens in org.springframework.validation.beanvalidation.SpringValidatorAdapter, in the getArgumentsForConstraint() method:

          arguments.add(new DefaultMessageSourceResolvable(codes, field));
          arguments.addAll(descriptor.getAttributes().values());

          The Map returned by getAttributes() is a HashMap, where the entry/value ordering is implementation-dependent. The ordering is even depending on if you are using SUN JDK version 5 or 6.

          (I was planning to use Spring validation to perform validation and to localize the JSR-303/bean validation error messages, but that seems impossible with the current Spring implementation. Apparently using the plain javax.validation API and overriding javax.validation.MessageInterpolator to provide the user's locale as default is the way to go for now to localize a web application...)

          Show
          stela Stefan Larsson added a comment - The randomization happens in org.springframework.validation.beanvalidation.SpringValidatorAdapter, in the getArgumentsForConstraint() method: arguments.add(new DefaultMessageSourceResolvable(codes, field)); arguments.addAll(descriptor.getAttributes().values()); The Map returned by getAttributes() is a HashMap, where the entry/value ordering is implementation-dependent. The ordering is even depending on if you are using SUN JDK version 5 or 6. (I was planning to use Spring validation to perform validation and to localize the JSR-303/bean validation error messages, but that seems impossible with the current Spring implementation. Apparently using the plain javax.validation API and overriding javax.validation.MessageInterpolator to provide the user's locale as default is the way to go for now to localize a web application...)
          Hide
          kwitukiewicz Krzysztof Witukiewicz added a comment -

          I gave Spring a chance, and overwrote the method getArgumentsForConstraint in SpringValidatorAdapter (I needed support for groups, so I had to overwrite this class anyway) As was already mentioned, the problem is, that the order of message attributes can be non-deterministic. My idea was to generically enforce their order. It could be done in some well known properties file like this:

          ConstraintsArgumentsMappings.properties

          javax.validation.constraints.Size=min:max
          javax.validation.constraints.Future=
          javax.validation.constraints.Max=value

          The format is simple:

          <class name of the constraint>=<argument names>
          <argument names> ::= [<argument name>{:<argument name>}]

          The overwritten method returns an array, whose first element is the name of the field in error (like in the current implementation), and other elements are values of arguments in the specified order. Then, in your messages.properties, you can simply write:

          messages.properties

          Size.java.lang.String=Length of {0} has to be between {1} and {2}

          What do you think about my solution? Is it an overkill? I know it is still only a workaround, and you have to initialize the adapter in your controllers and then call its validate method explicitly. I attach the files below.

          Show
          kwitukiewicz Krzysztof Witukiewicz added a comment - I gave Spring a chance, and overwrote the method getArgumentsForConstraint in SpringValidatorAdapter (I needed support for groups, so I had to overwrite this class anyway) As was already mentioned, the problem is, that the order of message attributes can be non-deterministic. My idea was to generically enforce their order. It could be done in some well known properties file like this: ConstraintsArgumentsMappings.properties javax.validation.constraints.Size=min:max javax.validation.constraints.Future= javax.validation.constraints.Max=value The format is simple: <class name of the constraint>=<argument names> <argument names> ::= [<argument name>{:<argument name>}] The overwritten method returns an array, whose first element is the name of the field in error (like in the current implementation), and other elements are values of arguments in the specified order. Then, in your messages.properties, you can simply write: messages.properties Size.java.lang.String=Length of {0} has to be between {1} and {2} What do you think about my solution? Is it an overkill? I know it is still only a workaround, and you have to initialize the adapter in your controllers and then call its validate method explicitly. I attach the files below.
          Hide
          kwitukiewicz Krzysztof Witukiewicz added a comment -

          The adapter with overwritten method getArgumentsForConstraint

          Show
          kwitukiewicz Krzysztof Witukiewicz added a comment - The adapter with overwritten method getArgumentsForConstraint
          Hide
          kwitukiewicz Krzysztof Witukiewicz added a comment -

          Class that read the properties file with constraints' arguments' order

          Show
          kwitukiewicz Krzysztof Witukiewicz added a comment - Class that read the properties file with constraints' arguments' order
          Hide
          juergen.hoeller Juergen Hoeller added a comment -

          I've addressed this in two different ways for Spring 3.0.4:

          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}

          . There is no way to determine the actual declaration order in the annotation definition, so alphabetical is the best we can do here - as well as suppressing JSR-303's 'internal' attributes ("message", "groups", "payload"). External ordering metadata would be a bit overkill here, IMO, in particular given the other option below...

          As a second option, you may configure JSR-303's own message interpolation to operate against a Spring MessageSource: If you pass a MessageSource to LocalValidatorFactoryBean's "validationMessageSource" property (new in Spring 3.0.4), JSR-303's message keys will be resolved against that Spring-managed MessageSource and then interpolated by the validation provider - including named argument substition, as defined by JSR-303, but against messages from a Spring-managed resource bundle. That should be a powerful alternative to Spring field errors with numbered arguments.

          Note that the second option requires Hibernate Validator 4.1 or above on the classpath. It'll work with other validation providers as well, but at least Hib Val 4.1's ResourceBundleMessageInterpolator class needs to be available.

          Juergen

          Show
          juergen.hoeller Juergen Hoeller added a comment - I've addressed this in two different ways for Spring 3.0.4: 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} . There is no way to determine the actual declaration order in the annotation definition, so alphabetical is the best we can do here - as well as suppressing JSR-303's 'internal' attributes ("message", "groups", "payload"). External ordering metadata would be a bit overkill here, IMO, in particular given the other option below... As a second option, you may configure JSR-303's own message interpolation to operate against a Spring MessageSource: If you pass a MessageSource to LocalValidatorFactoryBean's "validationMessageSource" property (new in Spring 3.0.4), JSR-303's message keys will be resolved against that Spring-managed MessageSource and then interpolated by the validation provider - including named argument substition, as defined by JSR-303, but against messages from a Spring-managed resource bundle. That should be a powerful alternative to Spring field errors with numbered arguments. Note that the second option requires Hibernate Validator 4.1 or above on the classpath. It'll work with other validation providers as well, but at least Hib Val 4.1's ResourceBundleMessageInterpolator class needs to be available. Juergen
          Hide
          janning Janning Vygen added a comment -

          The second option does not work if you use messageSource.setAlwaysUseMessageFormat(true). We need this option because our translators need a consistent format (easier then to say "double every apostrophe only if your find things like {0}"). If the message contains {min} and {max} Spring tries to interpolate it and fails with

          Caused by: java.lang.IllegalArgumentException: can't parse argument number: min

          Show
          janning Janning Vygen added a comment - The second option does not work if you use messageSource.setAlwaysUseMessageFormat(true) . We need this option because our translators need a consistent format (easier then to say "double every apostrophe only if your find things like {0}"). If the message contains { min } and { max } Spring tries to interpolate it and fails with Caused by: java.lang.IllegalArgumentException: can't parse argument number: min

            People

            • Assignee:
              juergen.hoeller Juergen Hoeller
              Reporter:
              scottyfred Scott Frederick
              Last updater:
              Janning Vygen
            • Votes:
              3 Vote for this issue
              Watchers:
              9 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since last comment:
                3 years, 8 weeks, 4 days ago