Announcement Announcement Module
Collapse
No announcement yet.
Binding errors not displaying correct message Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Binding errors not displaying correct message

    I am having difficulty getting the correct error message to display when a binding error occurs. I am currently getting the following message:

    Failed to convert property value of type [java.lang.String] to required type [java.lang.Integer] for property myDate;
    nested exception is java.lang.IllegalArgumentException: Invalid YYYY in (DD/MM/YYYY) date string: 05/07/2006xx

    But what I really want is the message defined in my messages.properties file for typeMismatch.myDate

    I am sure that the Controller is able read the properties file. For example:

    logger.debug(getMessage("typeMismatch.myDate", null, null));

    produces the expected result.

    Any ideas would be most appreciated.


    --- Here are the details ---

    I have the following mywebapp-servlet.xml:

    <!-- see /src/messages.properties in the class path -->
    <bean id="messageSource" class="org.springframework.context.support.Resourc eBundleMessageSource">
    <property name="basename" value="messages" />
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.Intern alResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlVi ew" />
    <property name="prefix" value="/WEB-INF/jsp/" />
    <property name="suffix" value=".jsp" />
    </bean>


    My Controller is implemented as follows:

    public class MyController extends SimpleFormController implements MessageSourceAware {
    private MessageSource messageSource;

    public MyController() {
    super();
    }

    public void setMessageSource(MessageSource messageSource) {
    this.messageSource = messageSource;
    }

    public String getMessage(String code, Object[] args, String defaultMessage) {
    return messageSource.getMessage(code, args, defaultMessage, Locale.getDefault());
    }

    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    binder.registerCustomEditor(Integer.class, "myDate", new IntegerDatePropertyEditor());
    // ...
    }
    }

    And in my JSP:

    <form:input path="myDate"/>
    <form:errors path="myDate" cssClass="error" element="div"/>

    And finally in src/messages.properties:

    typeMismatch.myDate=Date format is invalid

  • #2
    It looks like you can get the Errors with ServletRequestDataBinder.getBindingResult so you might be able to do something like this:
    Code:
    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    binder.registerCustomEditor(Integer.class, "myDate", new IntegerDatePropertyEditor(binder.getBindingResult())
    }
    and in your IntegerDatePropertyEditory do an .addError(new ObjectError(....)) if the input does not look valid.

    That does seem convoluted though and I have no idea if it will work. Give it a shot and post about it.

    Comment


    • #3
      Yes that works. But I don't like having to do it though...

      To me it seemed that the problem lay with the ErrorsTag's bindStatus.



      Code:
      	public IntegerDatePropertyEditor(BindingResult errors) {
      		this.errors = errors;
      	}
      
      	public void setAsText(String text) throws IllegalArgumentException {
      		try {
      			setValue(IntegerDateUtils.getIntegerDateFromText(text));
      		} catch (IllegalArgumentException e) {
      			if (errors != null) {
      				errors.reject("typeMismatch");
      			}
      			throw e;
      		}
      	}

      Comment


      • #4
        Can you post the full code of your Controller. I expect that you overwrite the model with your own model, effectivly removing all binding messages and information.

        Comment


        • #5
          Hi there,

          Here's the Controller class implementation and it's base class.

          Cheers.

          Code:
          public class PersonEditController extends BaseController {
          
              /**
               * commandName used in the jsp form:form element
               */
              static final String PERSON_COMMAND_NAME = "personForm";
          
              /**
               * maps to /WEB-INF/jsp/person/display.jsp
               */
              static final String PERSON_VIEW = "person/display";
          
              public PersonEditController() {
                  super();
                  init();
              }
          
              protected void init() {
                  setCommandClass(PersonForm.class);
                  setCommandName(PERSON_COMMAND_NAME);
                  setFormView(PERSON_VIEW);
                  setSuccessView(PERSON_VIEW);
                  setValidators(new Validator[] { new PersonFormSaveValidator() });
              }
          
              private void refreshPersonForm(PersonForm form, PersonData personData) {
                  // retrieve some data from the database using parameters supplied from personData
                  // and set it on the form
                  // ...
              }
          
              @Override
              protected Object formBackingObject(HttpServletRequest request) throws Exception {
                  Long pk = ServletRequestUtils.getLongParameter(request,
                      "pk");
                  if (pk == null) {
                      String pk = (String) request.getAttribute("pk");
                      if (StringUtils.isBlank(pk)) {
                          throw new BusinessRuleException("Missing parameter: pk");
                      }
                  }
          
                  PersonForm form = new PersonForm();
                  PersonData personData = personManager.getPersonDataByPK(pk);
                  if (personData == null) {
                      // it's a new person
                      personData = new PersonData();
                      personData.setPrimaryKey(pk);
                  } else {
                      refreshPersonForm(form, personData);
                  }
                  form.setPersonData(personData);
                  return form;
              }
          
              @Override
              protected List<String> doSubmitAction(Object command) throws Exception {
                  PersonForm form = (PersonForm) command;
          
                  personManager.savePersonForm(form.getPersonData());
          
                  PersonData personData = personManager.getPersonDataByPK(form.getPersonData()
                          .getPK());
                  refreshPersonForm(form, personData);
                  form.setPersonData(personData);
          
                  List<String> messages = new ArrayList<String>();
                  messages.add("Person data updated.");
                  return messages;
              }
          
              @Override
              protected Map referenceData(HttpServletRequest request) throws Exception {
                  Map<String, Object> referenceData = new HashMap<String, Object>();
          
                  // ...
                  
                  List<CommentaryType> commentaryTypesList = commentaryManager.listAllCommentaryTypes();
                  referenceData.put("commentaryTypesList", commentaryTypesList);
          
                  return referenceData;
              }
          
              @Override
              protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)
                      throws Exception {
                  binder.setRequiredFields(new String[] { "personData.peerGroupId", "personData.productTypeId" });
                  binder.registerCustomEditor(Integer.class, "personData.applicationDate",
                      new IntegerDatePropertyEditor(binder.getBindingResult())); 
                  super.initBinder(request, binder);
              }
          
          }
          
          
          
          //----- Heres the base class -----
          
          public class BaseController extends SimpleFormController implements MessageSourceAware {
              private MessageSource messageSource;
              
              public BaseController() {
                  super();
              }
          
              /** This version calls doSubmitAction with the username */
              protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
                      Object command, BindException errors) throws Exception {
                  try {
                      List<String> messages = doSubmitAction(command);
                      Map<String, Object> controlModel = new HashMap<String, Object>();
                      controlModel.put(getCommandName(), command);
                      if (messages != null && !messages.isEmpty()) {
                          controlModel.put("resultMessages", messages);
                      }
                      return showForm(request, response, errors, controlModel);
                  } catch (BusinessRuleException bre) {
                      ModelAndView mav = new ModelAndView(getFormView());
                      mav.addObject("businessRuleException", bre.getMessage());
                      mav.addAllObjects(referenceData(request));
                      mav.addObject(getCommandName(), command);
                      return mav;
                  }
              }
          
              public MessageSource getMessageSource() {
                  return this.messageSource;
              }
              
              public void setMessageSource(MessageSource messageSource) {
                  this.messageSource = messageSource;
              }
          }

          Comment


          • #6
            Originally posted by goofy View Post
            Yes that works. But I don't like having to do it though...

            To me it seemed that the problem lay with the ErrorsTag's bindStatus.
            What's the actual problem left?

            Your controller code looks ok.

            Jörg

            Comment


            • #7
              Well it actually works now thanks to cwilkes.

              I just don't like the part where I have to give my PropertyEditor a reference to BindingResult and then having to do a errors.reject("typeMismatch"). I would have thought that when a PropertyEditor throws an IllegalArgumentException in setAsText(), that the framework would translate that to a "typeMismatch" bind error on that field. Or am I missing something?

              Comment


              • #8
                Actually exactly that works for me. I also have a custom property editor which throws IllegalArgumentException if the string could not be converted to an object. This IllegalArgumentException gets converted to a TypeMismatchException in BeanWrapperImpl, those (extending PropertyAccessException) get collected in AbstractPropertyAccessor and are rethrown in a PropertyBatchUpdateException. In the DataBinder the PropertyAccessExceptions are passed to a BindingErrorProcessor instance. Spring comes with one implementation for it, the DefaultBindingErrorProcessor. That one does the following:

                Code:
                public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
                  // Create field error with the exceptions's code, e.g. "typeMismatch".
                  String field = ex.getPropertyChangeEvent().getPropertyName();
                  bject value = ex.getPropertyChangeEvent().getNewValue();
                  String[] codes = bindingResult.resolveMessageCodes(ex.getErrorCode(), field);
                  Object[] arguments = getArgumentsForBindError(bindingResult.getObjectName(), field);
                  bindingResult.addError(new FieldError(
                		bindingResult.getObjectName(), field, value, true,
                		codes, arguments, ex.getLocalizedMessage()));
                }
                That means it should result in "typeMismatch" errors on the BindingResult. For me that works. I wonder how you can "break" that.

                Jörg

                Comment


                • #9
                  IllegalArgumentException instead of typeMismatch

                  Hello,

                  I'm encountering the same problem today using spring 2.0.6 (also with 2.0.5):
                  Code:
                  Failed to convert property value of type [java.lang.String] to required type [java.lang.Double] for property gridTgCol[1][2]; nested exception is java.lang.IllegalArgumentException: Unparseable number: "d"
                  why not a typeMismatch?

                  I've set:
                  Code:
                  NumberFormat nf = NumberFormat.getNumberInstance(request.getLocale());
                          binder.registerCustomEditor( Double.class, null, new CustomNumberEditor(Double.class, nf, true));
                  it always worked fine for me... what's wrong? what's changed?

                  thanks

                  Comment

                  Working...
                  X