Chapter 8. Validate Your Products with a Validator

One of the most commonly expected behaviors of any web application is that it should validate user data. Every time a user submits data into our web application, it needs to be validated. This is to prevent security attacks, wrong data, or simple user errors. We don't have control over what the user may type while submitting data into our web application. For example, they may type some text instead of a date, they may forget to fill a mandatory field, or suppose we used 12-character lengths for a field in the database and the user entered 15-character length data, then the data cannot be saved in the database. Similarly, there are lots of ways a user can feed incorrect data into our web application. If we accept those values as valid, then it will create errors and bugs when we process such inputs. This chapter will explain the basics of setting up validation with Spring MVC.

After finishing this chapter, you will have a clear idea about the following:

  • JSR-303 Bean Validation
  • Custom validation
  • Spring validation

Bean Validation

Java Bean Validation (JSR-303) is a Java specification that allows us to express validation constraints on objects via annotations. It allows the APIs to validate and report violations. Hibernate Validator is the reference implementation of the Bean Validation specification. We are going to use Hibernate Validator for validation.

You can see the available Bean Validation annotations at the following site:

https://docs.oracle.com/javaee/7/tutorial/bean-validation001.htm .

Time for action - adding Bean Validation support

In this section, we will see how to validate a form submission in a Spring MVC application. In our project, we have the Add new product form already. Now let's add some validation to that form:

  1. Open pom.xml, which you will find under the root directory of the actual project.
  2. You will be able to see some tabs at the bottom of the pom.xml file. Select the Dependencies tab and click on the Add button of the Dependencies section.
  3. A Select Dependency window will appear; enter Group Id as org.hibernate, Artifact Id as hibernate-validator, Version as 5.2.4.Final, select Scope as compile, click the OK button, and save pom.xml.
  4. Open our Product domain class and add the @Pattern (javax.validation.constraints.Pattern) annotation at the top of the productId field, as follows:
          @Pattern(regexp="P[1-9]+", message="        
          {Pattern.Product.productId.validation}") 
          private String productId; 
    
  5. Similarly, add the @Size, @Min, @Digits, and @NotNull (javax.validation.constraints.*) annotations on the top of the name and unitPrice fields respectively, as follows:
          @Size(min=4, max=50, message="
          {Size.Product.name.validation}") 
          private String name; 
        
          @Min(value=0, message="      
          {Min.Product.unitPrice.validation}")
    @Digits(integer=8, 
          fraction=2, message="     
          {Digits.Product.unitPrice.validation}")
    @NotNull(message= "
          {NotNull.Product.unitPrice.validation}") 
          private BigDecimal unitPrice; 
    
  6. Open our message source file messages.properties from /src/main/resources in your project and add the following entries into it:
          Pattern.Product.productId.validation = Invalid product ID.
          It should start with character P followed by number. 
     
          Size.Product.name.validation = Invalid product name. It 
          should be minimum 4 characters to maximum 50 characters long. 
     
          Min.Product.unitPrice.validation = Unit price is Invalid. It 
          cannot have negative values. 
          Digits.Product.unitPrice.validation = Unit price is 
          Invalid.It can have maximum of 2 digit fraction and 8 digit 
          integer.  
          NotNull.Product.unitPrice.validation = Unit price is Invalid. 
          It cannot be empty. 
          Min.Product.unitPrice.validation = Unit price is Invalid. It 
          cannot be negative value. 
    
  7. Open our ProductController class and change the processAddNewProductForm request mapping method by adding a @Valid (javax.validation.Valid) annotation in front of the newProduct parameter. After finishing that, your processAddNewProductForm method signature should look as follows:
          public String     
          processAddNewProductForm(@ModelAttribute("newProduct")
          @Valid Product newProduct, BindingResult result, 
          HttpServletRequest request)  
    
  8. Now, within the body of the processAddNewProductForm method, add the following condition as the first statement:
          if(result.hasErrors()) { 
             return "addProduct"; 
          } 
    
  9. Open our addProduct.jsp from src/main/webapp/WEB-INF/views/ in your project and add the <form:errors> tag for the productId, name, and unitPrice input elements. For example, the product ID input tag will have the <form:errors> tag beside it, as follows:
          <form:input id="productId" path="productId" type="text"     
          class="form:input-large"/> 
          <form:errors path="productId" cssClass="text-danger"/>
    

    Remember, the path attribute value should be the same as the corresponding input tag.

  10. Now, add one global <form:errors> tag within the <form:form> tag, as follows:
          <form:errors path="*" cssClass="alert alert-danger" 
          element="div"/> 
    
  11. Add bean configuration for LocalValidatorFactoryBean in our web application context configuration file WebApplicationContextConfig.java, as follows:
          @Bean(name = "validator") 
          public LocalValidatorFactoryBean validator() { 
             LocalValidatorFactoryBean bean = new
             LocalValidatorFactoryBean(); 
             bean.setValidationMessageSource(messageSource()); 
             return bean; 
          } 
    
  12. Finally, override the getValidator method in WebApplicationContextConfig to configure our validator bean as the default validator, as follows:
          @Override 
          public Validator getValidator(){ 
             return validator(); 
          } 
    

    Note

    Note that here the return type is org.springframework.validation.Validator

  13. Now run our application and enter the URL http://localhost:8080/webstore/market/products/add. We will see a webpage showing a web form to add product info. Without entering any values in the form, simply click on the Add button. You will see validation messages at the top of the form, as shown in the following screenshot:
    Time for action - adding Bean Validation support

    The Add new product web form showing validation message.

What just happened?

Since we decided to use the Bean Validation (JSR-303) specification, we needed an implementation of the Bean Validation specification, and we decided to use a Hibernate Validator implementation in our project; thus we need to add that JAR to our project as a dependency. That's what we did in steps 1 through 3.

From steps 4 and 5, we added some javax.validation.constraints annotations such as @Pattern, @Size, @Min, @Digits, and @NotNull on our domain class (Product.java) fields. Using these annotations, we can define validation constraints on fields. There are more validation constraint annotations available under the javax.validation.constraints package. Just for demonstration purposes, we have used a couple of annotations; you can check out the Bean Validation documentation for all available lists of constraints.

For example, take the @Pattern annotation on top of the productId field; it will check whether the given value for the field matches the regular expression that is specified in the regexp attribute of the @Pattern annotation. In our example, we just enforce that the value given for the productId field should start with the character P and should be followed by digits:

@Pattern(regexp="P[1-9]+", message="   {Pattern.Product.productId.validation}") 
private String productId; 

The message attribute of every validation annotation is just acting as a key to the actual message from the message source file (messages.properties). In our case, we specified Pattern.Product.productId.validation as the key, so we need to define the actual validation message in the message source file. That's why we added some message entries in step 6. If you noticed the corresponding value for the key Pattern.Product.productId.validation in the messages.properties file, you will notice the following value:

Pattern.Product.productId.validation = Invalid product ID. It should start with character P followed by number. 

Tip

You can even add localized error messages in the corresponding message source file if you want. For example, if you want to show error messages in Dutch, simply add error message entries in the messages_nl.properties file as well. During validation, this message source will be automatically picked up by Spring MVC based on the chosen locale.

We defined the validation constraints in our domain object, and also defined the validation error messages in our message source file. What else do we need to do? We need to tell our controller to validate the form submission request. We did that through steps 7 and 8 in the processAddNewProductForm method:

@RequestMapping(value = "/products/add", method = RequestMethod.POST) 
public String processAddNewProductForm(@ModelAttribute("newProduct") @Valid Product newProduct, BindingResult result, HttpServletRequest request) { 
    
   if(result.hasErrors()) { 
      return "addProduct"; 
   } 
 
   String[] suppressedFields = result.getSuppressedFields(); 
   if (suppressedFields.length > 0) { 
      throw new RuntimeException("Attempting to bind disallowed fields: " + StringUtils.arrayToCommaDelimitedString(suppressedFields)); 
   } 
 
   MultipartFile productImage = newProduct.getProductImage(); 
   String rootDirectory = request.getSession().getServletContext().getRealPath("/"); 
 
      if (productImage!=null && !productImage.isEmpty()) { 
         try { 
         productImage.transferTo(new File(rootDirectory+"resources\images"+ newProduct.getProductId() + ".png")); 
         } catch (Exception e) { 
         throw new RuntimeException("Product Image saving failed", e); 
      } 
      } 
 
   productService.addProduct(newProduct); 
   return "redirect:/market/products"; 
} 

We first annotated our method parameter newProduct with the @Valid (javax.validation.Valid) annotation. By doing so, Spring MVC will use the Bean Validation framework to validate the newProduct object. As you already know, the newProduct object is our form-backed bean. After validating the incoming form bean (newProduct), Spring MVC will store the results in the result object; this is again another method parameter of our processAddNewProductForm method.

In step 8 we simply checked whether the result object contains any errors. If so, we redirected to the same Add new product page; otherwise we proceeded to add productToBeAdded to our repository.

So far everything is fine. At first we defined the constraints on our domain object and defined the error messages in the message source file (messages.properties). Later we validated and checked the validation result in the controller's form processing method (processAddNewProductForm), but we haven't said how to show the error messages in the view file. Here comes Spring MVC's special <form:errors> tag to the rescue.

We added this tag for the productId, name, and unitPrice input elements in step 9. If any of the input fields failed during validation, the corresponding error message will be picked up by this <form:errors> tag:

<form:errors path="productId" cssClass="text-danger"/> 

The path attribute is used to identify the field in the form bean to look for errors, and the cssClass attribute will be used to style the error message. I have used Bootstrap's style class text-danger, but you can use any valid CSS- style class that you prefer to apply on the error message.

Similarly, in step 10, we have added a global <form:errors> tag to show all error messages as a consolidated view at the top of the form:

<form:errors path="*" cssClass="alert alert-danger" element="div"/> 

Here we have used the "*" symbol for the path attribute, which that means we want to show all the errors and element attributes, just indicating which type of element Spring MVC should use to list all the errors.

So far, we have done all the coding-related stuff that is needed to enable validation, but we have to do one final configuration in our web application context to enable validation; that is we need to introduce the Bean Validation framework to our Spring MVC. In steps 11 and 12 we did just that. We have defined a bean for LocalValidatorFactoryBean (org.springframework.validation.beanvalidation.LocalValidatorFactoryBean). This LocalValidatorFactoryBean will initiate the Hibernate Validator during the booting of our application:

@Bean(name = "validator") 
public LocalValidatorFactoryBean validator() { 
   LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); 
   bean.setValidationMessageSource(messageSource()); 
   return bean; 
} 

The setValidationMessageSource method of LocalValidatorFactoryBean indicates which message source bean it should look to for error messages. Since, in Chapter 6, Internalize Your Store with Interceptor, we already configured message sources in our web application context, we just use that bean, which is why we assigned the value messageSource() as the value for the setValidationMessageSource method. You will see a bean definition under the method messageSource() already in our web application context.

And finally, we introduced our validator bean to Spring MVC by overriding the getValidator method:

@Override 
public Validator getValidator(){ 
   return validator(); 
} 

That is all we did to enable validation; now, if you run our application and bring the Add new product page using the http://localhost:8080/webstore/market/products/add URL, you can see the empty form ready to submit. If you submit that form without filling in any information, you will see error messages in red.

Have a go hero - adding more validation in the Add new product page

I have just added validation for the first three fields in the Product domain class; you can extend the validation for the remaining fields. And try to add localisedlocalized error messages for the validation you are defining.

Here are some hints you can try out:

  • Add validation to show a validation message in case the category field is empty
  • Try to add validation to the unitsInStock field to validate that the minimum number of Units in stock allowed is zero
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.85.72