Announcement Announcement Module
Collapse
No announcement yet.
Problems using JSR-303 ConstraintValidator Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Problems using JSR-303 ConstraintValidator

    I have a Roo-generated Spring MVC app to which I'm adding web flow. I'm using:
    • SWF 2.1.1.RELEASE
    • Spring 3.0.4.RELEASE
    • Hibernate Validator 4.0.2.GA (JSR-303 provider)
    • Roo 1.0.2
    Domain Model

    I have a simple entity called Book, which has a title and a publisher:

    Code:
    @Entity
    @RooJavaBean
    @RooToString
    @RooEntity
    @BookConstraint(message = "Oops!")
    public class Book implements Serializable {
    
        @Column(length = 30)
        @NotNull
        @Size(min = 1, max = 30)
        private String title;
    
        @ManyToOne(targetEntity = Publisher.class)
        @NotNull
        @JoinColumn
        private Publisher publisher;
    }
    Validation

    Apart from the above field-level JSR-303 annotations, I also have a custom ConstraintValidator with its corresponding annotation, as follows:

    Code:
    public class BookConstraintValidator
        implements ConstraintValidator<BookConstraint, Book>
    {
        @Override
        public void initialize(BookConstraint constraintAnnotation) {
            // Empty
        }
    
        @Override
        public boolean isValid(Book book, ConstraintValidatorContext context) {
            return false;  // i.e. validation is hard-coded to fail for now
        }
    }
    Code:
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = BookConstraintValidator.class)
    public @interface BookConstraint {
        
        String message() default "{some.error.code}";
        
        Class<?>[] groups() default {};
        
        Class<? extends Payload>[] payload() default {};
    }
    Works with MVC

    This all works nicely when adding a Book via straight Spring MVC (i.e. outside of SWF). The validation fails as expected, and the view displays the configured "Oops!" error message.

    Integration of JSR-303 with SWF

    Rather than duplicate all the above validation rules in SWF-style validators, I have set up SWF to delegate to my JSR-303 validation logic via a LocalValidatorFactoryBean. However when I add a new Book via SWF, I get this exception:

    Code:
    org.springframework.webflow.execution.FlowExecutionException: Exception thrown in state 'enterBookDetails' of flow 'flows/newBook'
        at org.springframework.webflow.engine.impl.FlowExecutionImpl.wrap(FlowExecutionImpl.java:569)
        at org.springframework.webflow.engine.impl.FlowExecutionImpl.resume(FlowExecutionImpl.java:263)
        at org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:169)
        at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:183)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:788)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:717)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    ...
    Caused by: java.lang.IllegalArgumentException: The expression string to parse is required and must not be empty
        at org.springframework.util.Assert.hasText(Assert.java:162)
        at org.springframework.binding.expression.spel.SpringELExpressionParser.parseExpression(SpringELExpressionParser.java:74)
        at org.springframework.binding.message.MessageContextErrors.rejectValue(MessageContextErrors.java:86)
        at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:92)
        at com.example.books.domain.validate.BookValidator.validate(BookValidator.java:17)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:186)
        at org.springframework.webflow.validation.ValidationHelper.invokeValidatorDefaultValidateMethod(ValidationHelper.java:256)
        at org.springframework.webflow.validation.ValidationHelper.invokeModelValidator(ValidationHelper.java:185)
        at org.springframework.webflow.validation.ValidationHelper.validate(ValidationHelper.java:104)
        at org.springframework.webflow.mvc.view.AbstractMvcView.validate(AbstractMvcView.java:625)
        at org.springframework.webflow.mvc.view.AbstractMvcView.processUserEvent(AbstractMvcView.java:217)
        at org.springframework.webflow.engine.ViewState.handleEvent(ViewState.java:224)
        at org.springframework.webflow.engine.ViewState.resume(ViewState.java:196)
        at org.springframework.webflow.engine.Flow.resume(Flow.java:545)
        at org.springframework.webflow.engine.impl.FlowExecutionImpl.resume(FlowExecutionImpl.java:259)
        ... 47 more
    java.lang.IllegalArgumentException
    The expression string to parse is required and must not be empty
    java.lang.IllegalArgumentException: The expression string to parse is required and must not be empty
        at org.springframework.util.Assert.hasText(Assert.java:162)
        at org.springframework.binding.expression.spel.SpringELExpressionParser.parseExpression(SpringELExpressionParser.java:74)
        at org.springframework.binding.message.MessageContextErrors.rejectValue(MessageContextErrors.java:86)
        at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:92)
        at com.example.books.domain.validate.BookValidator.validate(BookValidator.java:17)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:186)
        at org.springframework.webflow.validation.ValidationHelper.invokeValidatorDefaultValidateMethod(ValidationHelper.java:256)
        at org.springframework.webflow.validation.ValidationHelper.invokeModelValidator(ValidationHelper.java:185)
        at org.springframework.webflow.validation.ValidationHelper.validate(ValidationHelper.java:104)
        at org.springframework.webflow.mvc.view.AbstractMvcView.validate(AbstractMvcView.java:625)
        at org.springframework.webflow.mvc.view.AbstractMvcView.processUserEvent(AbstractMvcView.java:217)
        at org.springframework.webflow.engine.ViewState.handleEvent(ViewState.java:224)
        at org.springframework.webflow.engine.ViewState.resume(ViewState.java:196)
        at org.springframework.webflow.engine.Flow.resume(Flow.java:545)
        at org.springframework.webflow.engine.impl.FlowExecutionImpl.resume(FlowExecutionImpl.java:259)
        at org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:169)
        at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:183)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:788)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:717)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
        at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
        at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:362)
        at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
        at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
        at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:726)
        at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
        at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:285)
        at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:126)
    ...
    I'm looking into this as we speak, but I'm wondering if anyone else has come across a similar problem, or has managed to get a ConstraintValidator working with SWF.

    Thanks in advance for any help.
    Last edited by Andrew Swan; Aug 25th, 2010, 09:23 PM. Reason: Updated title to reflect the exact problem

  • #2
    Solved

    From my investigation, it turns out that MessageContextErrors doesn't correctly handle global errors; I've logged this as SWF-1389.

    The workaround for SWF users is to extend LocalValidatorFactoryBean as follows:

    Code:
    /**
     * Extends the Spring class to support global errors as well as field errors
     */
    public class FixedValidatorFactory extends LocalValidatorFactoryBean {
    
        @Override
        public void validate(Object target, Errors errors) {
            Set<ConstraintViolation<Object>> result = validate(target);
            for (ConstraintViolation<Object> violation : result) {
                String field = violation.getPropertyPath().toString();
                String errorCode =
                    violation.getConstraintDescriptor().getAnnotation()
                    .annotationType().getSimpleName(); 
                Object[] errorArgs = getArgumentsForConstraint(
                        errors.getObjectName(), field, violation.getConstraintDescriptor()); 
                String defaultMessage = violation.getMessage();
                FieldError fieldError = errors.getFieldError(field);
                if (fieldError == null) {
                    // Global error
                    errors.reject(errorCode, errorArgs, defaultMessage);
                }
                else if (!fieldError.isBindingFailure()) {
                    // Non-binding field error
                    try {
                        errors.rejectValue(field, errorCode, errorArgs, defaultMessage);
                    }
                    catch (NotReadablePropertyException ex) {
                        throw new IllegalStateException("JSR-303 validated property '" +
                                field + "' does not have a corresponding accessor for" +
                                " Spring data binding - check your DataBinder's" +
                                " configuration (bean property versus direct field" +
                                " access)", ex);
                    }
                }
            }
        }
    }
    Last edited by Andrew Swan; Sep 12th, 2010, 08:00 PM. Reason: Updated the bug number after it was moved to SWF

    Comment

    Working...
    X