Announcement Announcement Module
Collapse
No announcement yet.
JSR-303 Validation, Spring MVC and messages.properties Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • JSR-303 Validation, Spring MVC and messages.properties

    I'm using JSR-303 validators (hibernate validation) to validate a bean being bound in one of my controller methods. The validation works, but the output back to the web-layer fails with the following error:

    Code:
    "java.lang.IllegalArgumentException: can't parse argument number mandatory.field"
    I know this is because one of my custom validation annotations has the following messages property:

    Code:
    String message() default "{mandatory.field}";
    It's failing trying to interpolate that message. My question is: can I make use the same messages.properties file I use for other internationalized messages for this purpose?

    In my spring context, I have a message source:

    <bean id="messageSource" class="org.springframework.context.support.Reloada bleResourceBundleMessageSource">
    <property name="basename" value="/WEB-INF/messages/messages" />
    <property name="cacheSeconds" value="0" />
    </bean>

    I can't see how to wire this message source into the validation process. I know there is a MessageSourceResourceBundle class that seems to be taking me in the right direction (along with LocaleContextMessageInterpolator and the Hibernate message interpolator) but MessageSourceResourceBundle takes the locale as a constructor arg and I want the message to be resolved on a per-user basis so as to be properly internationalized.

    I can't help but think I'm missing something blindingly obvious. Any ideas?

  • #2
    Hi,

    Yes you can use messages.properties files to show validation errors. You currently have to implement your own MessageInterpolator which will delegate the message codes to MessageSource. Apparently this functionality should be coming into Spring 3.1 with a more delicate implementation (http://jira.springframework.org/browse/SPR-6582). See the code example.

    MessageInterpolator:
    Code:
    public class SpringMessageSourceMessageInterpolator implements MessageInterpolator,
        MessageSourceAware, InitializingBean {
    
        private MessageSource messageSource;
        
        @Override
        public String interpolate(String messageTemplate, Context context) {
            return messageSource.getMessage(messageTemplate, new Object[]{}, Locale.getDefault());
        }
    
        @Override
        public String interpolate(String messageTemplate, Context context, Locale locale) {
            return messageSource.getMessage(messageTemplate, new Object[]{}, locale);
        }
    
        @Override
        public void setMessageSource(MessageSource messageSource) {
            this.messageSource = messageSource;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            if (messageSource == null) {
                throw new IllegalStateException("MessageSource was not injected, could not initialize " 
                        + this.getClass().getSimpleName());
            }
        }
    
    }
    applicationContext.xml:
    Code:
      <mvc:annotation-driven validator="validator" />
      
      <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="messageInterpolator">
          <bean class="com.foo.SpringMessageSourceMessageInterpolator" />
        </property>
      </bean>
    After this you can use for example:
    Code:
    @NotNull(message="error.foo.null")
    private String foo;
    Which would map to your localized messages.properties file with code "error.foo.null". Hope that helps you or some other (this seems to be a rather old question) who happens to stumble upon this same problem.

    Comment


    • #3
      I'm having a similar problem as described above. I've added the SpringMessageSourceMessageInterpolator and when I put a validation annotation on my DTO like this:
      Code:
      @Size(min = 1, max = 10, message = "someDto.street")
      The app returns what I have in my message.properties for 'someDto.street'...

      So far so good..
      Now I've modified my message.properties like this to have a common error for NLZipcodeConstraints and Size. I want to inject the fieldname into those other properties:

      Code:
      someDto.street=Straatnaam
      someDto.zipCode=Postcode
      NLZipcodeConstraint={1} is geen geldige postcode! --- {0} --- {2} --- {3} --- {4} 
      Size={1} moet tussen de {2} en {4} karakters lang zijn. --- {0} --- {3} --- {5}
      In my JSP this results in:
      HTML Code:
      - someDto.street moet tussen de 1 en 10 karakters lang zijn. --- street --- [Ljava.lang.Class;@f191d9 --- [Ljava.lang.Class;@19e798a
      - someDto.zipCode is geen geldige postcode! --- zipCode --- [Ljava.lang.Class;@13d633d --- [Ljava.lang.Class;@13e699d --- {4}
      As example: Size
      {0} is resolved as 'street'
      {1} is resolved as 'someDto.street'

      While I was expecting something like:
      HTML Code:
      - Straatnaam moet tussen de 1 en 10 karakters lang zijn. --- street --- [Ljava.lang.Class;@f191d9 --- [Ljava.lang.Class;@19e798a
      - Postcode is geen geldige postcode! --- zipCode --- [Ljava.lang.Class;@13d633d --- [Ljava.lang.Class;@13e699d --- {4}
      Note: The added params like '--- {0} --- {2} --- {3} --- {4} ' are there for debugging purposes.

      When I put a breakpoint at:

      Code:
      public String interpolate(final String messageTemplate, final Context context, final Locale locale) {
              return messageSource.getMessage(messageTemplate, new Object[] {}, locale);
          }
      it stops there, with the param 'someDto.street' which is succesfully resolved to 'Straatnaam'.. But somewhere along the way it is trashed as soon as the properties like 'Size' and 'NLZipcodeConstraint' are put in the propertyfile..

      Anyone has a clue what I'm doing wrong?

      Comment


      • #4
        If you want the message to be interpolated, you must surround it in curly braces like thus:

        Code:
        @Size(min = 1, max = 10, message = "{someDto.street}")

        Comment


        • #5
          Hey Arkalius,

          I've tried adding curly brackets like that:
          Code:
          @Size(min = 1, max = 10, message = "{someDto.street}")
          Then I got a:
          Code:
          ERROR  org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/sprintTest].[spring] - Servlet.service() for serv
          let spring threw exception
          org.springframework.context.NoSuchMessageException: No message found under code '{someDto.street}' for locale 'en_US'.
                  at org.springframework.context.support.AbstractMessageSource.getMessage(AbstractMessageSource.java:135)
                  at org.springframework.context.support.AbstractApplicationContext.getMessage(AbstractApplicationContext.java:119
          2)
                  at com.bol.springtest.SpringMessageSourceMessageInterpolator.interpolate(SpringMessageSourceMessageInterpolator.
          java:32)
                  at org.springframework.validation.beanvalidation.LocaleContextMessageInterpolator.interpolate(LocaleContextMessa
          geInterpolator.java:53)
          So no luck there.. :S I tried not injecting the messageSource, like this:

          Code:
          <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
          	<!-- DISABLED 
          	<property name="messageInterpolator">
                		<bean class="com.bol.springtest.SpringMessageSourceMessageInterpolator" />
              	</property>
              	 -->
          </bean>
          leads me back to an message like this:

          Code:
          {someDto.street} moet tussen de 1 en 10 karakters lang zijn. --- street --- [Ljava.lang.Class;@1a0cc7f --- [Ljava.lang.Class;@1d1bc39
          What I want to do is:
          messages.properties:
          someDto.zipCode=Postcode
          NLZipcodeConstraint={1} is geen geldige postcode!

          Which leads to:
          Postcode is geen geldige postcode!

          All I can get is:
          - zipCode is geen geldige postcode!
          - {someDto.zipCode} is geen geldige postcode!

          Bottomline - I'm still stuck :-)
          Has anyone got a working example perhaps?

          Comment


          • #6
            I somehow fixed the problem.. Atleast the messages are displayed right now.

            messages.properties
            Code:
            #Validator messages
            NLZipcodeConstraint={0} is geen geldige postcode! 
            Size={0} moet tussen de {2} en {4} karakters lang zijn. 
            #Properties reflect the items in the form.
            someDtoAttr.street=Straatnaam
            someDtoAttr.zipCode=Postcode
            form.jsp
            Code:
            <sf:form commandName="someDtoAttr" method="POST">
            	<s:message code="someDtoAttr.street"/>: <sf:input path="street"/><sf:errors path="street"/><br/>
            	<s:message code="someDtoAttr.zipCode"/>: <sf:input path="zipCode"/><sf:errors path="zipCode"/><br/>
            	<input type="submit" value="OK" />
            	<a href="../index.shtml">Cancel</a>			
            </sf:form>
            someDto.java
            Code:
                @NotNull
                @Size(min = 1, max = 10)
                private String street;
            This gives the wanted result.. So it looks like the property from the message.properties is reflected to the commandName of the form. I also found out that you can provide an override property to the @Size (message) which you can use instead of the Size property.. But then again, it looks like the override does not work when you have the Size property defined.. Not sure what the use is though..

            Atleast it works ... :S

            Comment


            • #7
              Just for the record, for the predefined annotation stuff it seems to me (I'm using spring 3.0.3) that a custom interpolator is not needed. I am able to specify custom codes in the annotations simply by wrapping them in curly braces.

              Code:
              @Pattern(regexp="[0-9]+", message="{someerror.code}"
              Also, default messages for the annotations can be defined as
              Code:
              <constraint_name>.<command_name>.<field_name>
              eg
              Size=the {0} field must be between {2} and {4} characters long
              or
              Size.book.description=the book''s description must be between {2} and {4} characters long
              CORRECTION: It appears the message="{someerror.code}" works only if the validation messages are in the spec's defined ValidationMessages.properties. If you have them in your messages.properties, perhaps this is the appropriate class to use:

              Code:
              package com.foo.web.validation;
              
              import java.util.Locale;
              
              import javax.validation.MessageInterpolator;
              
              import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
              import org.springframework.beans.factory.InitializingBean;
              import org.springframework.beans.factory.annotation.Autowired;
              import org.springframework.context.MessageSource;
              import org.springframework.context.MessageSourceAware;
              import org.springframework.context.NoSuchMessageException;
              
              public class CustomSpringMessageSourceInterpolator extends ResourceBundleMessageInterpolator implements MessageInterpolator, MessageSourceAware, InitializingBean {
              
              	@Autowired
                  private MessageSource messageSource;
              
                  @Override
              	public String interpolate(String messageTemplate, Context context) {
                      try {
                      	return messageSource.getMessage(messageTemplate, new Object[]{}, Locale.getDefault());
                      } catch (NoSuchMessageException e) {
                      	return super.interpolate(messageTemplate, context);
                      }
                  }
              
                  @Override
              	public String interpolate(String messageTemplate, Context context, Locale locale) {
                      try {
                      	return messageSource.getMessage(messageTemplate, new Object[]{}, locale);
                      } catch (NoSuchMessageException e) {
                      	return super.interpolate(messageTemplate, context, locale);
                      }
                  }
              
                  public void setMessageSource(MessageSource messageSource) {
                      this.messageSource = messageSource;
                  }
              
                  public void afterPropertiesSet() throws Exception {
                      if (messageSource == null) {
                          throw new IllegalStateException("MessageSource was not injected, could not initialize " 
                                  + this.getClass().getSimpleName());
                      }
                  }
              
              }
              Last edited by adam_jh; Jun 29th, 2010, 08:48 AM. Reason: correction

              Comment


              • #8
                Hi Adam, the message resolution did break in my case (am using spring 3.1) because the "{" / "}" were arround the messageTemplate property and could not be resolved. Fixed it that way:

                Code:
                import java.util.Locale;
                
                import javax.validation.MessageInterpolator;
                
                import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
                import org.springframework.beans.factory.InitializingBean;
                import org.springframework.context.MessageSource;
                import org.springframework.context.MessageSourceAware;
                import org.springframework.context.NoSuchMessageException;
                
                public class CustomSpringMessageSourceInterpolator extends ResourceBundleMessageInterpolator implements MessageInterpolator, MessageSourceAware, InitializingBean {
                
                	private MessageSource messageSource;
                
                	@Override
                	public String interpolate(String messageTemplate, Context context) {
                		try {
                			String messageTemplateSpring = removeFirstAndLast(messageTemplate);
                			return messageSource.getMessage(messageTemplateSpring, new Object[] {}, Locale.getDefault());
                		} catch (NoSuchMessageException e) {
                			return super.interpolate(messageTemplate, context);
                		}
                	}
                
                	@Override
                	public String interpolate(String messageTemplate, Context context, Locale locale) {
                		try {
                			String messageTemplateSpring = removeFirstAndLast(messageTemplate);
                			return messageSource.getMessage(messageTemplateSpring, new Object[] {}, locale);
                		} catch (NoSuchMessageException e) {
                			return super.interpolate(messageTemplate, context, locale);
                		}
                	}
                
                	public void setMessageSource(MessageSource messageSource) {
                		this.messageSource = messageSource;
                	}
                
                	public void afterPropertiesSet() throws Exception {
                		if (messageSource == null) {
                			throw new IllegalStateException("MessageSource was not injected, could not initialize " + this.getClass().getSimpleName());
                		}
                	}
                
                	private static String removeFirstAndLast(String string) {
                		return string.substring(1, string.length() - 1);
                	}
                
                }

                Comment

                Working...
                X