JSR-303 Bean Validation and Spring MVC in a Sling OSGi container

After reading Having “fun” with JSR-303 Beans Validation and OSGi + Spring DM, I thought I would attempt to bootstrap JSR-303 Beans Validation in Spring in a Sling OSGi container.  Unfortunately, a few changes have been made to the Hibernate API since Magnus' articles, so a few updates to his version are required.  Additionally, I want to enable JSR-303 Beans Validation in the Sling/Spring MVC environment I previously wrote about.  This presented a few additional challenges, so I'll also walk through the steps to enable JSR-303 Beans Validation in a Spring MVC application context running within a Sling OSGi container so that you can use JSR-303 Beans Validation as described in the Spring documentation.


Dependencies

Since I've already created a project that will allow me to run Spring MVC in a Sling OSGi container, I'm going to start with my previous project.  First, let's update our globaldispatcher-servlet.xml file to scan the new packages we'll be using:

...
  <context:component-scan base-package="net.jasonday.examples.sling">
    <context:exclude-filter type="regex" expression="net\.jasonday\.examples\.sling\.spring\.mvc\.sling\..*"/>
  </context:component-scan>
...

Next, let's add our new dependencies. 
Java Bean Validation API v1.0.0.GA (need OSGi version)
Hibernate Validator v4.2.0.Final

The Hibernate Validator v4.2.0.Final jar file is already OSGi enabled.  However, we're going to need an OSGi enabled version of Java Bean Validation API v1.0.0.GA - Spring Bundle Repository to the rescue again!  Let's add our dependencies to our Maven project:

...
  <dependencies>
    ...
    <!-- Validation dependencies -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>4.2.0.Final</version>
    </dependency>
    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>com.springsource.javax.validation</artifactId>
      <version>1.0.0.GA</version>
    </dependency>
  </dependencies>
...

Now let's add the OSGi Bundle imports that we're going to need from these two new dependencies.  Add the validator imports to the <Import-Package> element within the Maven Bundle Plugin configuration in the pom.xml file like so

...
            <Import-Package>
              ...
              <!-- Validator -->
              javax.validation,
              javax.validation.bootstrap,
              javax.validation.constraints,
              javax.validation.spi,
              org.hibernate.validator,
              org.springframework.validation.beanvalidation,
              org.springframework.validation,

              !*
            </Import-Package>
...


Bootstrap JSR-303 Beans Validation in Spring

With the help of Magnus Jungsbluth, we have a head start on what we'll need to bootstrap JSR-303 Beans Validation in Spring.  Unfortunately, as I mentioned above, there are a few changes in the version of Hibernate we'll be using (v4.2.0.Final).  Therefore, we'll need to make a few changes.  We still need to create an implementation of ValidationProviderResolver that will work in an OSGi environment, so we'll follow Magnus' lead:

package net.jasonday.examples.sling.spring.validator.spring;

import java.util.ArrayList;
import java.util.List;

import javax.validation.ValidationProviderResolver;
import javax.validation.spi.ValidationProvider;

import org.hibernate.validator.HibernateValidator;

/**
 * OSGi classpath aware {@link javax.validation.ValidationProviderResolver
 * ValidationProviderResolver}.
 * 
 */
public class HibernateValidationProviderResolver implements ValidationProviderResolver {
  @Override
  public List<ValidationProvider<?>> getValidationProviders() {
    List<ValidationProvider<?>> providers = new ArrayList<ValidationProvider<?>>(1);
    providers.add(new HibernateValidator());
    return providers;
  }
}

This class will be used to build our ValidatorFactory that knows how to find all of the validation classes in our bundle since they're not necessarily exposed on the classpath.  Next, we need to create a class that will build and return a ValidatorFactory instance that we can use.  Springs documentation suggests using the LocalValidatorFactoryBean class.  Unfortunately, this class doesn't provide a hook to customize the ValidationProviderResolver (see LocalValidatorFactoryBean.afterPropertiesSet() method), so we have to bootstrap JSR-303 on our own.  Let's create a Spring configuration file that builds and exposes an instance of ValidatorFactory into the Spring context.

package net.jasonday.examples.sling.spring.validator.spring;

import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.bootstrap.ProviderSpecificBootstrap;

import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.springframework.context.annotation.Configuration;

/**
 * Configure our validators.
 * 
 */
@Configuration
public class ValidationConfiguration {
  public ValidatorFactory validatorFactory() {
    // configure and build an instance of ValidatorFactory
    ProviderSpecificBootstrap<HibernateValidatorConfiguration> validationBootStrap = Validation
        .byProvider(HibernateValidator.class);

    // bootstrap to properly resolve in an OSGi environment
    validationBootStrap.providerResolver(new HibernateValidationProviderResolver());

    HibernateValidatorConfiguration configure = validationBootStrap.configure();

    // now that we've done configuring the ValidatorFactory, let's build it
    ValidatorFactory validatorFactory = configure.buildValidatorFactory();

    return validatorFactory;
  }
}

So, now we have an instance of ValidatorFactory in our Spring application context that we can use to inject into any Bean we wish.  But...we want to do more than just inject an instance of ValidatorFactory, we want to use all of Spring MVCs' validation capabilities.  In order to do this, we're going to need to do two things.  First, we need to configure the ValidatorFactory to use Spring to autowire our constraint classes.  This can be accomplished by configuring our ValidatorFactory with a Spring enabled implementation of ConstraintValidatorFactory.  Fortunately, Spring provides an implementation in the form of the SpringConstraintValidatorFactory class.  Second, we need to build, expose and register an instance of org.springframework.validation.Validator with Spring MVC in order for it to recognize the JSR-303 Bean Validation classes.  Normally, this second step would be accomplished by configuring an instance of LocalValidatorFactoryBean, because it implements the org.springframework.validation.Validator interface.  So, let's create a new class that implements the Validator, ValidatorFactory and org.springframework.validation.Validator interfaces.  Happily, Spring offers a convenience class we can use as a base class for some of these operations with SpringValidatorAdapter.  So, let's create a subclass of SpringValidatorAdapter that also exposes the ValidatorFactory interface rather than registering a single Bean that exposes only the ValidatorFactory interface.

package net.jasonday.examples.sling.spring.validator.spring;

import javax.validation.ConstraintValidatorFactory;
import javax.validation.MessageInterpolator;
import javax.validation.TraversableResolver;
import javax.validation.Validator;
import javax.validation.ValidatorContext;
import javax.validation.ValidatorFactory;

import org.springframework.validation.beanvalidation.SpringValidatorAdapter;

/**
 * Subclass of
 * {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter
 * SpringValidatorAdapter} that will expose a
 * {@link javax.validation.ValidatorFactory ValidatorFactory} through the
 * {@link org.springframework.validation.Validator} interface.
 * 
 */
public class OsgiValidatorFactoryBean extends SpringValidatorAdapter implements ValidatorFactory {

  /**
   * Constructor that takes an instance of {@link ValidatorFactory} to expose
   * through the {@link org.springframework.validation.Validator} interface.
   * @param validatorFactory ValidatorFactory instance
   */
  public OsgiValidatorFactoryBean(ValidatorFactory validatorFactory) {
    super(validatorFactory.getValidator());
    this.validatorFactory = validatorFactory;
  }

  private ValidatorFactory validatorFactory;

  public Validator getValidator() {
    return this.validatorFactory.getValidator();
  }

  public ValidatorContext usingContext() {
    return this.validatorFactory.usingContext();
  }

  public MessageInterpolator getMessageInterpolator() {
    return this.validatorFactory.getMessageInterpolator();
  }

  public TraversableResolver getTraversableResolver() {
    return this.validatorFactory.getTraversableResolver();
  }

  public ConstraintValidatorFactory getConstraintValidatorFactory() {
    return this.validatorFactory.getConstraintValidatorFactory();
  }
}

Now we need to update our ValidationConfiguration class to build and register an instance of OsgiValidatorFactoryBean rather than only an instance of ValidatorFactory.

package net.jasonday.examples.sling.spring.validator.spring;

import javax.inject.Inject;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.bootstrap.ProviderSpecificBootstrap;

import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;

/**
 * Configure our validators.
 * 
 */
@Configuration
public class ValidationConfiguration {
  private ApplicationContext applicationContext;

  public ApplicationContext getApplicationContext() {
    return applicationContext;
  }

  @Inject
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }

  /**
   * Build and expose an instance of {@link OsgiValidatorFactoryBean}. This
   * class exposes the {@link javax.validation.Validator},
   * {@link javax.validation.ValidatorFactory} and
   * {@link org.springframework.validation.Validator} interfaces.
   * @return OsgiValidatorFactoryBean An instance of OsgiValidatorFactoryBean
   */
  @Bean
  public OsgiValidatorFactoryBean osgiValidatorFactoryBean() {
    // configure and build an instance of ValidatorFactory
    ProviderSpecificBootstrap<HibernateValidatorConfiguration> validationBootStrap = Validation
        .byProvider(HibernateValidator.class);

    // bootstrap to properly resolve in an OSGi environment
    validationBootStrap.providerResolver(new HibernateValidationProviderResolver());

    HibernateValidatorConfiguration configure = validationBootStrap.configure();

    // configure Spring to autowire our constraints
    configure.constraintValidatorFactory(new SpringConstraintValidatorFactory(this.applicationContext
        .getAutowireCapableBeanFactory()));

    // now that we've done configuring the ValidatorFactory, let's build it
    ValidatorFactory validatorFactory = configure.buildValidatorFactory();

    // now build and return an OsgiValidatorFactoryBean
    return new OsgiValidatorFactoryBean(validatorFactory);
  }
}

 

Configure Spring MVC to use the JSR-303 validator

We now have a Bean exposed under the Validator, ValidatorFactory and org.springframework.validation.Validator interfaces, but Spring MVC doesn't know about it due to the manual bootstrapping we're performing.  If we were using the <mvc:annotation-driven/> tag as described in Springs' documentation, the class that bootstraps Spring MVC would detect JSR-303 on the classpath and set it up itself.  Unfortunately, we're not using this tag - and we can't due to the limitations of the OSGi environment - so we have to manually configure Spring MVC to use the org.springframework.validation.Validator Bean we've created.  Fortunately, this is fairly simple since we've already customized the HandlerAdapter Spring MVC is using in our SlingHandlerConfiguration class.  We will first need to customize an implementation of WebBindingInitializer to use the org.springframework.validation.Validator (OsgiValidatorFactoryBean) we expose through the ValidationConfiguration class.  Let's add a Bean to our SlingHandlerConfiguration class:

...
@Configuration
public class SlingHandlerConfiguration {
  /**
   * Configure an instance of
   * {@link org.springframework.web.bind.support.WebBindingInitializer
   * WebBindingInitializer} with a
   * {@link org.springframework.validation.Validator Validator}.
   * @param validator Validator instance with which to customize the
   * WebBindingInitializer
   * @return WebBindingInitializer A configured WebBindingInitializer
   */
  @Bean
  public WebBindingInitializer webBindingInitializer(Validator validator) {
    ConfigurableWebBindingInitializer configurableWebBindingInitializer = new ConfigurableWebBindingInitializer();
    configurableWebBindingInitializer.setValidator(validator);
    return configurableWebBindingInitializer;
  }
...

Now, let's modify the SlingHandlerConfiguration.handlerAdapter() method to take an instance of WebBindingInitializer to use in the configuration of our HandlerAdapter.

...
@Configuration
public class SlingHandlerConfiguration {
...
  /**
   * Configures a {@link org.springframework.web.servlet.HandlerAdapter
   * HandlerAdapter} instance for use in a Sling environment by overriding the
   * {@link org.springframework.web.util.UrlPathHelper UrlPathHelper} with a
   * {@link SlingUrlPathHelper} instance.
   * 
   * @param webBindingInitializer An instance of
   * {@link org.springframework.web.bind.support.WebBindingInitializer} to
   * configure the adapter
   * @return HandlerAdapter An instance of HandlerAdapter for use in a Sling
   * environment
   */
  @Bean
  public HandlerAdapter handlerAdapter(WebBindingInitializer webBindingInitializer) {
    // override the AnnotationMethodHandlerAdapters' UrlPathHelper
    AnnotationMethodHandlerAdapter handlerAdapter = new AnnotationMethodHandlerAdapter();
    handlerAdapter.setUrlPathHelper(new SlingUrlPathHelper());
    
    handlerAdapter.setWebBindingInitializer(webBindingInitializer);

    // configure the custom WebArgumentResolvers
    handlerAdapter.setCustomArgumentResolvers(new WebArgumentResolver[] { new ResourceResolverArgumentResolver(),
        new SlingRequestArgumentResolver() });
    return handlerAdapter;
  }
...


Test the @Valid annotation on a command object

We've configured Spring MVC in a Sling container to use the Hibernate Validator for validation, but now we need to test it.  Let's create a model class that we can perform validation on to ensure everything works.  Create a simple test model class with some validation constraints applied to the fields:

 
package net.jasonday.examples.sling.model;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class MyModel {
  @NotNull
  private String firstName;

  @NotNull
  @Size(min = 2)
  private String lastName;

  @Min(2)
  private int age;

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }
}

Once we have our Model class, let's map it to the MySlingController.helloWorld(...) method and validate the input by using the @Valid annotation.  Make sure we also pass in the BindingResult in order to get the list of errors.

package net.jasonday.examples.sling.spring.mvc.controller;

import java.io.IOException;

import javax.servlet.ServletResponse;
import javax.validation.Valid;

import net.jasonday.examples.sling.model.MyModel;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Example Spring MVC Controller class.
 * 
 */
@Controller
public class MySlingController {
  /**
   * Hello world method that will map to
   * <strong>examples/sling/spring/mvc/mySlingController</strong> controller
   * types.
   * <p>
   * @param servletResponse ServletResponse object for writing out to the
   * response
   * @param myModel The model
   * @param bindingResult The binding results
   * @throws IOException Thrown if there is an exception writing out to the
   * response
   */
  @RequestMapping("examples/sling/spring/mvc/mySlingController")
  public void helloWorld(ServletResponse servletResponse, @Valid
  MyModel myModel, BindingResult bindingResult) throws IOException {
    // write out "Hello world!" to test the response
    servletResponse.getWriter().println("Hello world!<br>");

    // display the errors on the page
    for (ObjectError error : bindingResult.getAllErrors()) {
      servletResponse.getWriter().println("BindingError with message '" + error.getDefaultMessage() + "'<br>");
    }
  }
}

Now head out to the page with a few URL parameters to populate the fields to test the validations out.  For example, going to /content/mySlingController.html shoud produce something like this:

Hello world!
BindingError with message 'must be greater than or equal to 2'
BindingError with message 'may not be null'
BindingError with message 'may not be null'

while going to /content/mySlingController.html?firstName=Jason&lastName=Day should only produce the following:

Hello world!
BindingError with message 'must be greater than or equal to 2'

 

Test Spring injecting the validators

We've now seen that the @Valid annotation is properly working, but we still want to verify and test dependency injecting a custom constraint.  To do this, we're going to need to create a Spring managed service, a @Constraint, and a ConstraintValidator that uses an instance of a Spring managed service.  First, the Spring managed service interface:

package net.jasonday.examples.sling.service;

public interface FirstNameService {
  public boolean isValid(String firstName);
}

Now it's implementation:

package net.jasonday.examples.sling.service.stub;

import org.springframework.stereotype.Component;

import net.jasonday.examples.sling.service.FirstNameService;

@Component
public class FirstNameServiceImpl implements FirstNameService {
  @Override
  public boolean isValid(String firstName) {
    return "Jason".equals(firstName);
  }
}

Now let's create a ConstraintValidator that will use our Spring managed service to check the validity of the MyModel.firstName field using the FirstNameService we just defined .

package net.jasonday.examples.sling.validators;

import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import net.jasonday.examples.sling.constraints.CheckFirstName;
import net.jasonday.examples.sling.service.FirstNameService;

public class CheckFirstNameValidator implements ConstraintValidator<CheckFirstName, String> {
  private FirstNameService firstNameService;

  public FirstNameService getFirstNameService() {
    return firstNameService;
  }

  @Inject
  public void setFirstNameService(FirstNameService firstNameService) {
    this.firstNameService = firstNameService;
  }

  @Override
  public void initialize(CheckFirstName constraintAnnotation) {
    // nothing to initialize
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    if (null == value) {
      return true;
    }
    
    return firstNameService.isValid(value);
  }
}

And the @Constraint itself:

package net.jasonday.examples.sling.constraints;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import net.jasonday.examples.sling.validators.CheckFirstNameValidator;

@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckFirstNameValidator.class)
@Documented
public @interface CheckFirstName {
  String message() default "{net.jasonday.examples.sling.constraints.checkfirstname}";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};
}

Finally, let's update apply the CheckFirstName constraint to our MyModel.firstName field:

...
public class MyModel {
  @NotNull
  @CheckFirstName
  private String firstName;
...
}

You can test it out by going to a URL with the firstName parameter set to something other than Jason.  For example, /content/mySlingController.html?firstName=Test&lastName=Day should now produce the following page:

Hello world!
BindingError with message 'must be greater than or equal to 2'
BindingError with message '{net.jasonday.examples.sling.constraints.checkfirstname}'

Congratulations, you now have a JSR-303 enabled Spring MVC running in a Sling OSGi container!

 

Comments

Hi,Thank you for the solution, I was locked with the same problems.The only problem is that in this case, libraries that detects hibernate validators (for example myfaces) won't detect it.Not far important, JSR303 is usable as is.Best regards, Charlie

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.