Book Image

Spring MVC Blueprints

By : Sherwin John C. Tragura
Book Image

Spring MVC Blueprints

By: Sherwin John C. Tragura

Overview of this book

Spring MVC is the ideal tool to build modern web applications on the server side. With the arrival of Spring Boot, developers can really focus on the code and deliver great value, leveraging the rich Spring ecosystem with minimal configuration. Spring makes it simple to create RESTful applications, interact with social services, communicate with modern databases, secure your system, and make your code modular and easy to test. It is also easy to deploy the result on different cloud providers. This book starts all the necessary topics in starting a Spring MVC-based application. Moving ahead it explains how to design model objects to handle file objects. save files into a data store and how Spring MVC behaves when an application deals with uploading and downloading files. Further it highlights form transactions and the user of Validation Framework as the tool in validating data input. It shows how to create a customer feedback system which does not require a username or password to log in. It will show you the soft side of Spring MVC where layout and presentation are given importance. Later it will discuss how to use Spring Web Flow on top of Spring MVC to create better web applications. Moving ahead, it will teach you how create an Invoice Module that receives and transport data using Web Services By the end of the book you will be able to create efficient and flexible real-time web applications using all the frameworks in Spring MVC.
Table of Contents (16 chapters)
Spring MVC Blueprints
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface

Configuring the Spring container


Before we create controllers for our PWP project, the Spring container must be ready for bean injections and component declarations. First, our Spring MVC container must be annotation-driven so that we can utilize the annotation stereotypes used by the current Spring Framework specification in configuring containers.

To enable the use of annotations inside classes, the following tag must be appearing in the pwp-servlet.xml:

    <mvc:annotation-driven /> 

Second, when the <annotation-driven> tag is enabled, the container must automatically scan all component classes that are part of the Spring MVC web project. This will be enabled through inserting the following tag into the pwp-servlet.xml.

    <context:component-scan  
      base-package="org.packt.personal.web.portal" /> 

The base-package attribute indicates the base folder of the development directory structure (src) where all components and beans are located. Declaring this tag enables auto-detection of the bean components in the project, which includes the controllers.

Drop all static resources like the CSS files into the ch01/webapp folder. Declare the default servlet handler in order to allow the access of those static resources from the root of the web application even though the DispatcherServlet is registered at the context root ch01.

    <mvc:default-servlet-handler /> 

Then, use the <mvc:resources> element to point to the location of the static resources with a specific public URL pattern.

    <mvc:resources mapping="/css/**" location="/css/" /> 

The complete XML-based Spring container of PWP is shown as follows:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xmlns:aop="http://www.springframework.org/schema/aop" 
  xmlns:context="http://www.springframework.org/schema/context" 
  xmlns:oxm="http://www.springframework.org/schema/oxm" 
  xmlns:tx="http://www.springframework.org/schema/tx" 
  xmlns:mvc="http://www.springframework.org/schema/mvc" 
  xsi:schemaLocation="http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-4.1.xsd 
    http://www.springframework.org/schema/oxm
    http://www.springframework.org/schema/oxm/spring-oxm-4.1.xsd 
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/
         spring-beans-4.1.xsd 
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-4.1.xsd 
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/
         spring-context-4.1.xsd 
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd"> 
 
  <mvc:annotation-driven /> 
  <mvc:default-servlet-handler /> 
  <mvc:resources mapping="/css/**" location="/css/" /> 
</beans> 

The JavaConfig equivalent of our XML-based container will have this equivalent code:

import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.ComponentScan; 
// Rest of the imports in sources 
 
@Configuration 
@ComponentScan(basePackages="org.packt.personal.web.portal") 
@EnableWebMvc 
public class PWPConfiguration extends WebMvcConfigurerAdapter { 
 
  @Override 
    public void addResourceHandlers(ResourceHandlerRegistry      registry) { 
      registry.addResourceHandler("/css/**").       addResourceLocations("/css/"); 
    } 
} 

The @EnableWebMvc annotation is equivalent to <mvc:annotation-driven /> in the XML-based version . Lastly, the @ComponentScan annotation is equivalent to the <context:component-scan base-package=" org.packt.personal.web.portal "/> we have in our pwp-servlet.xml. To allow recognition to static resources, the application must override the resource handler method.

Creating the controllers

The PWP project will consist of four content pages each having its own use request transaction. Users can update three of the four contents using session handling. In order to implement services to each page, controller class must be created with the stereotype @Controller instead of extending controller base classes or Spring-specific APIs. The home page of PWP has the following controller declaration:

@Controller 
public class IndexController {  } 

Each controller class has more than service or handler methods. Each service method is being mapped to the user's request by the DispatcherServlet. A valid service or handler method must have a @RequestMapping stereotype written on top of the method or function signature. A sample controller with a service method is shown as follows:

@Controller 
public class IndexController { 
 
  @RequestMapping(value="/index", method=RequestMethod.GET) 
  public String getIndex(Model model) { 
    model.addAttribute("greetings",  "Welcome Page"); 
    return "index"; 
  } 
} 

A service method is mapped to at least one URL or path, and has an HTTP method declared. The preceding sample getIndex() is mapped to the /index.html and processes a GET request transaction.

There are actually four general classifications of a controller and these are the following:

  • A controller that calls a page only: These controllers simply provide services that call view names like the following sample:

        @Controller 
          public class IndexController { 
 
            @RequestMapping(value="/index", 
            method=RequestMethod.GET) 
            public String getIndex() { 
              return "index"; 
            } 
          } 
  • A controller that brings model(s) to views: This group of controllers transport models to the view for presentation purposes. A controller can pass an org.springframework.web.ModelAndView to successful view pages with an object containing all the necessary objects, like in the following snippet:

        @Controller 
          public class IndexController { 
 
            @RequestMapping(value="/index", 
            method=RequestMethod.GET) 
            public ModelAndView getIndex() { 
              Map<String, Object> model = new HashMap<>(); 
              model.put("greetings",  "Welcome Page"); 
              return new ModelAndView("index", "model", model); 
            } 
 
          } 
  • On the other hand, the most modern and simple way of transporting model data is through the use of the org.springframework.ui.Model object, wherein all objects transported to the views are either @ModelAttribute or @SessionAttribute. A simple sample on this approach is given as follows:

        @Controller 
          public class IndexController { 
 
            @RequestMapping(value="/index", 
            method=RequestMethod.GET) 
            public String getIndex(Model model) { 
              model.addAttribute("greetings",  "Welcome Page"); 
              return "index"; 
            } 
          } 
  • A controller that has multi-services or multi-actions: Controllers that have more than one service or action:

        @Controller 
        public class IndexController { 
 
          @RequestMapping(value="/index", 
          method=RequestMethod.GET) 
          public String getIndex(Model model){ 
            model.addAttribute("greetings",  "Welcome Page"); 
            return "index"; 
          } 
 
          @RequestMapping(value="/index_post", 
            method=RequestMethod.POST) 
            public String postIndex(Model model) { 
              model.addAttribute("greetings",  "Welcome Page"); 
              return "index"; 
            } 
        } 
  • A controller that accepts request parameters (form controllers): These controllers have a form view and a success view. The form view accepts request parameters from the client. Then the request will be mapped to the service method, which will process all the request data. The result will be transported as a model, or as an attribute, to the corresponding view. Following is a sample snippet:

         @Controller 
         @SessionAttributes(value={"statusSess", "homeSess"}) 
         @RequestMapping("/pwp/index_update") 
         public class IndexUpdateController { 
 
         @Autowired 
         private Validator indexValidator; 
 
         @InitBinder("homeForm") 
         public void initBinder(WebDataBinder binder) { 
         binder.setValidator(indexValidator); 
         } 
 
         @RequestMapping(method=RequestMethod.GET) 
 
         public String initForm(Model model) { 
         Home homeForm = new Home(); 
         model.addAttribute("homeForm", homeForm); 
         return "index_update"; 
         } 
 
         @RequestMapping(method=RequestMethod.POST) 
         public String submitForm(Model model, 
         @ModelAttribute("homeForm") 
         @Validated Home homeForm, BindingResult binding) { 
         model.addAttribute("homeForm", homeForm); 
         String returnVal = "index"; 
         if(binding.hasErrors()) { 
         returnVal = "index_update"; 
         } else { 
         model.addAttribute("homeSess", homeForm); 
         model.addAttribute("statusSess", "undefault"); 
         } 
         return returnVal; 
         } 
         }

The PWP controllers

This project utilizes all the preceding controller classification except for multi-action controllers. Following is the finished product of the PWP that highlights the home page. All content pages have an equivalent update transaction for updating their content. The home page, in particular, is invoked through the URL http://localhost:8080/ch01/pwp/index.html where ch01 is the context root. With this URL, DispatcherServlet will look for the controller that has the service method bearing the path /pwp/index. Once the match is found, the service method calls the appropriate view with the data model, if there is one. The final step will be loading the content desired by the user.

A complete home page controller class with the default values of the cone is shown as follows:

package org.packt.personal.web.portal.controller; 
 
import java.util.ArrayList; 
import java.util.Date; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
 
import org.packt.personal.web.portal.model.form.Home; 
import org.packt.personal.web.portal.model.form.PostMessage; 
// Rest of the code in the sources 
 
@Controller 
@SessionAttributes("posts") 
@RequestMapping("pwp") 
public class IndexController { 
 
  @ModelAttribute("posts") 
  public List<PostMessage> newPosts() { 
    return initPost(); 
  } 
 
  @RequestMapping(value="/index", method=RequestMethod.GET) 
  public String getIndex(Model model,  @ModelAttribute("posts") 
    List<PostMessage> posts, 
    @ModelAttribute("postForm") PostMessage postForm) { 
 
      Home home = new Home(); 
      home.setMessage(initMessage()); 
      home.setQuote(initQuote()); 
      model.addAttribute("home",  home); 
        if(posts == null) posts = newPosts(); 
        if(validatePost(postForm)) { 
          postForm.setDatePosted(new Date()); 
          posts.add(postForm); 
        } 
        model.addAttribute("posts",  posts); 
      return "index"; 
  } 
 
  @RequestMapping(value="/index_redirect", 
    method=RequestMethod.GET) 
  public RedirectView updateIndex() { 
    return new RedirectView("/ch01/pwp/index_update.html"); 
  } 
 
  public String initQuote() { 
    String message = "Twenty years from now you will be more 
      disappointed by the things .......... -Mark Twain"; 
    return message; 
  } 
 
  public String initMessage() { 
    String message = "Having a positive outlook on life is a      crucial part of finding inspiration. In the paragraph above,      ......"; 
    return message; 
  } 
 
  public List<PostMessage> initPost() { 
    List<PostMessage> posts = new ArrayList<>(); 
    PostMessage post = new PostMessage(); 
    post.setSubject("Welcome!"); 
    post.setDatePosted(new Date()); 
    post.setPostedMsg("Hello visitors!Feelfree to post on my 
    portal!"); 
 
    posts.add(post); 
    return posts; 
  } 
 
  public boolean validatePost(PostMessage post) { 
    try { 
      if(post.getSubject().trim().length() == 0 || 
      post.getPostedMsg().trim().length() == 0) { 
        return false; 
      } 
    } 
    catch(Exception e) { 
      return false; 
    } 
    return true; 
  } 
} 

The method getIndex() is responsible for loading the home page initially. The default content for message and content are given by two methods, namely initMessage() and initPost(). The initPost() method initially populates the right navigation panel of the home page where all the posts will be listed. This posting feature will be elaborated on further in the next topics.

Generally, all these default methods are essential to generate the home page when no session data are available to replace this default content.

The update page on the home page will be invoked by clicking the Update button. Each portal has an Update button except for the Reach Out page. After clicking the Update button on the home page, DispatcherServlet will look for the service that has the URL path /pwp/index_redirect. There are two general ways to implement redirection and these are the following:

  • Using the RedirectView API: This process of redirection is done through implementation of the handler method that returns org.springframework.web.servlet.view.RedirectView. Following are some snippets that show how to implement redirection.

        @RequestMapping(value="/index_redirect",
                        method=RequestMethod.GET) 
          public RedirectView updateIndex() { 
            return new RedirectView("/ch01/pwp/index_update.html"); 
          } 
 
          @RequestMapping(value="/index_redirect",
                          method=RequestMethod.GET) 
            public String updateIndex() { 
              return (redirect:"/ch01/pwp/index_update.html"); 
            } 
  • Using the <c:url> JSTL tag: This process is a direct process of redirection and being configured on the view page.

            <c:url var=indexUpdate" value="/pwp/index_update.html"/> 
            <a href="<c:out value="${indexUpdate }"/>">Update Home</a>  
    

After redirection to the update page, the home page has two important pieces of content information that need to be updated, and these are the message and quote portions of the page:

All update pages have validators to check the attributes of the form data. On the home page, users cannot input quotes or messages that are less than 50 and more than 500 alphanumeric characters. Validation and object type checking occurs only if our controllers have form views. A controller that has form and success views is implemented in a different way. Following is a template of a form controller that is used by the PWP project:

package org.packt.personal.web.portal.controller; 
 
import java.util.ArrayList; 
import java.util.Date; 
import java.util.List; 
 
import org.packt.personal.web.portal.converter.AgeConverter; 
import org.packt.personal.web.portal.converter.BirthDateConverter; 
// Rest of the codes in the sources 
@Controller 
@SessionAttributes(value = {"pStatusSess", "personSess"}) 
@RequestMapping("/pwp/personal_update") 
public class PersonalUpdateController { 
 
  @Autowired 
  Validator personalValidator; 
 
  @InitBinder("personForm") 
  public void initBinder(WebDataBinder binder) { 
    binder.setValidator(personalValidator); 
    binder.registerCustomEditor(Integer.class, "biography.age", 
    new AgeConverter()); 
    binder.registerCustomEditor(Integer.class, "education.year", 
    new YearConverter()); 
    binder.registerCustomEditor(Date.class, "biography.birthDate", 
    new BirthDateConverter()); 
  } 
 
  @RequestMapping(method=RequestMethod.GET) 
  public String initForm(Model model) { 
    Biography bio = new Biography(); 
    Education educ = new Education(); 
    Personal personForm = new Personal(); 
    personForm.setBiography(bio); 
    personForm.setEducation(educ); 
    references(model); 
    model.addAttribute("personForm", personForm); 
    return "personal_update"; 
  } 
 
  @RequestMapping(method=RequestMethod.POST) 
  public String submitForm(Model model, 
  @ModelAttribute("personForm") @Validated Personal personForm, 
  BindingResult binding) { 
    model.addAttribute("personForm", personForm); 
    String returnVal = "personal"; 
    if(binding.hasErrors()) { 
      references(model); 
      returnVal = "personal_update"; 
    } else { 
      model.addAttribute("personSess", personForm); 
      model.addAttribute("pStatusSess", "undefault"); 
    } 
    return returnVal; 
 
  } 
 
  public void references(Model model) { 
    List<String> hobbiesList = new ArrayList<String>(); 
    hobbiesList.add("Not Applicable"); 
    hobbiesList.add("Singing"); 
    hobbiesList.add("Painting"); 
    hobbiesList.add("Traveling"); 
    hobbiesList.add("Writing"); 
    hobbiesList.add("Swimming"); 
    model.addAttribute("hobbiesList", hobbiesList); 
    // Rest of the code in sources 
  } 
} 

Although there is no restriction as to what, and how many, handler methods are needed in a typical controller class, a form controller has to maintain having only one @RequestMapping stereotype declared at the class level. The path indicated must be the controller's form view.

The preceding controller has two service operations namely the initForm() and submitForm() methods. The initForm() service is always a GET method, which always initializes the form view. It also binds the form domain object that contains the request data from the user. The submitForm() is a POST service that is executed after form submission. This automatically extracts the validated domain object from the form and transports any model to the result views.

The references() method is used to populate the form components found in the form view. Java Collection Framework (JCF) is used to contain all the data which can be hardcoded or which is from a data store.

The usual exception during form-handling happens when the form-backing object, or the model, is not set to bind with the form view. Whenever the browser calls the initForm(), the following exception will be encountered.

java.lang.IllegalStateException: 
  Neither BindingResult nor plain target object 
    for bean name 'home' available as request attribute 
      at o.s.w.s.s.BindStatus.<init>(BindStatus.java:141) 

In order for submitForm() to access the form-backing object, the @ModelAttribute annotation is needed to be declared in its parameters. An @ModelAttribute local parameter indicates that the argument will be retrieved from the form view. If not present in the form view, the argument must be instantiated first in the initForm(), and bound to the form view. Moreover, Spring MVC throws an exception when errors occur during request binding, by default. That is why in submitForm(), one of the arguments is for BindIngResult where we can extract all errors during the request transaction. The BindingResult argument needs to be positioned right after our form-backing object, which is a rare case when it comes to the @RequestMapping order of arguments.

If this rule is violated, the following exception will be thrown:

java.lang.IllegalStateException:
Errors/BindingResult argument declared without preceding model    attribute 

Using @ModelAttribute and @SessionAttributes

The Personal Web Portal (PWP) application does not have database connectivity since the main highlight in this chapter are the raw Spring MVC 4 components. Also, it has no Spring security attached to it from the core, so there are no login/logout transaction for the user. All controllers in the PWP use a lot of @ModelAttribute and @SessionAttributes data.

The @ModelAttribute data are actually the same with request attributes. These are objects created from one single request dispatching. Moreover, these data are also called request-scoped data, which means that these data values are only valid within one request-response transaction.

One use of @ModelAttribute is to bind the form domain object to the form view page. On an incoming request transaction, any models with @ModelAttributes are called before any controller handler method. The following initForm() method is one example of this scenario.

@RequestMapping(method=RequestMethod.GET) 
  public String initForm(Model model, 
  @ModelAttribute("profesionalForm") 
  Professional professionalForm) { 
    return "professional_update"; 
  } 
 
@RequestMapping(method=RequestMethod.GET) 
  public String initForm(Model model) { 
    Professional professionalForm = new Professional(); 
 
    model.addAttribute("professionalForm", professionalForm); 
    references(model); 
    return "professional_update"; 
  } 
  public void references(Model model) { 
    List<String> hobbiesList = new ArrayList<String>(); 
    hobbiesList.add("Not Applicable"); 
    hobbiesList.add("Singing"); 
    hobbiesList.add("Painting"); 
    hobbiesList.add("Traveling"); 
    hobbiesList.add("Writing"); 
    hobbiesList.add("Swimming"); 
    model.addAttribute("hobbiesList", hobbiesList); 
 
    List<String> readingsList = new ArrayList<String>(); 
    readingsList.add("Not Applicable"); 
    readingsList.add("Novel"); 
    readingsList.add("Magazine"); 
    readingsList.add("Newspaper"); 
    readingsList.add("Diaries"); 
    model.addAttribute("readingsList", readingsList); 
 
    List<String> educLevelList = new ArrayList<String>(); 
    educLevelList.add("Not Applicable"); 
    educLevelList.add("Doctoral"); 
    educLevelList.add("Masters"); 
    educLevelList.add("College"); 
    educLevelList.add("Vocational"); 
    educLevelList.add("High School"); 
    model.addAttribute("educLevelList", educLevelList); 
  } 

To initialize the @ModelAttribute, it must be injected with an object value just like we have in the PWP project:

@ModelAttribute("greetings") 
  public List<String> getGreetings() { 
    return Greetings.allGreets(); 
  } 

The @SessionAttributes, on the other hand, are used by the controller to declare session objects during session handling. Equally, these attributes are the session attributes in a typical JEE web component. As long as the session is not terminated, session data are always accessible for update and retrieval. Sessions were used in the PWP project to store values for all the dynamic content transactions. Here is a code snippet that shows how to declare, initialize and update values that are @SessionAttributes.

@Controller 
@SessionAttributes("posts") 
@RequestMapping("pwp") 
public class IndexController { 
 
  @ModelAttribute("posts") 
  public List<PostMessage> newPosts() { 
    return initPost(); 
  } 
 
  @RequestMapping(value="/index", method=RequestMethod.GET) 
  public String getIndex(Model model,  @ModelAttribute("posts") 
    List<PostMessage> posts, 
    @ModelAttribute("postForm") PostMessage postForm) { 
 
      Home home = new Home(); 
      home.setMessage(initMessage()); 
      home.setQuote(initQuote()); 
      model.addAttribute("home",  home); 
 
      if(posts == null) posts = newPosts(); 
      if(validatePost(postForm)) { 
        postForm.setDatePosted(new Date()); 
        posts.add(postForm); 
      } 
 
      model.addAttribute("posts",  posts); 
      return "index"; 
    } 
    // Other codes refer to sources 
} 

The @SessionAttributes are declared on the controller level and are initialized as with any @ModelAttribute. Always initialize the session data to avoid the following error:

Servlet.service() for servlet [pwp] in context with path [/ch01] threw exception [Expected session attribute 'posts'] with root cause
org.springframework.web.HttpSessionRequiredException: Expected session attribute 'posts'

In order for the service operations to access the session attribute argument, be sure to have a @ModelAttribute parameter on the local parameter of the service depicting the attribute data:

public String getIndex(Model model,  @ModelAttribute("posts") 
  List<PostMessage> posts, 
  @ModelAttribute("postForm") PostMessage postForm) { 
  } 

These two attributes trigger the information workflow of the PWP project, even without the help of any web services or data store.

Form domain objects

Binding to the form view using @ModelAttribute, forms domain objects that are plain POJOs, and are used to save request parameter values from the user. Sometimes these objects are also called form data models. See the following domain:

package org.packt.personal.web.portal.model.form; 
 
public class Home { 
 
  private String message; 
  private String quote; 
 
  public String getMessage() { 
    return message; 
  } 
 
  public void setMessage(String message) { 
    this.message = message; 
  } 
 
  public String getQuote() { 
    return quote; 
  } 
 
  public void setQuote(String quote) { 
    this.quote = quote; 
  } 
} 

Instantiation of this model will be found in the initForm() method and the binding to the form view page will be implemented using the Spring Form Taglib directive:

<%@ taglib prefix="form"  uri="http://www.springframework.org/tags/form"%> 

The Spring MVC <form> tag has its own form components that can be readily mapped to the setters of the form domain objects. These topics will be highlighted in Chapter 3Student Management Portal (SMP), of this book. The following JSP code snippet shows how the binding is done through the <form> tag:

<form:form commandName="homeForm" method="post"> 
  <div class="form_settings"> 
    <!-- insert the page content here --> 
    <h2>Update Inspirational Message</h2> 
    <p><form:textarea path="message" rows="8" cols="50"/></p> 
    <span><form:errors path="message" cssStyle="color:      #ff0000; "/></span> 
    <br/> <br/> <br/> 
 
    <h2>Update Best Quote (Required)</h2> 
    <p><form:textarea path="quote" rows="8" cols="50"/></p> 
    <span><form:errors path="quote"  cssStyle="color:      #ff0000; "/></span>  
    <br/> <br/> <br/> 
 
    <p style="padding-top: 15px; padding-left: 20px"><input      class="submit"  
    type="submit" value="Update Home Page"></p> 
  </div> 
  </form:form> 

There are instances when a form view page contains several request parameters which need to be subdivided into groups.

Given this situation, the best strategy is to decompose the huge flat domain POJO into several specific form domain models, each containing related data. Then, create a main POJO component class, which will hold all smaller domain models. This class will be represented as the form-backing object for the form view. A sample implementation of the preceding content page is shown as follows:

package org.packt.personal.web.portal.model.form; 
 
public class Personal { 
 
  private Biography biography; 
  private Education education; 
 
  public Biography getBiography() { 
    return biography; 
  } 
  public void setBiography(Biography biography) { 
    this.biography = biography; 
  } 
  public Education getEducation() { 
    return education; 
  } 
  public void setEducation(Education education) { 
    this.education = education; 
  } 
} 
 
package org.packt.personal.web.portal.model.form; 
import java.util.Date; 
import java.util.List; 
 
public class Biography { 
  private String firstName; 
  private String lastName; 
  private Integer age; 
  private Date birthDate; 
  private String location; 
  private String country; 
  private List<String> hobbies; 
  private List<String> readings; 
  // The getters and the setters 
} 
 
package org.packt.personal.web.portal.model.form; 
public class Education { 
 
  private String educLevel; 
  private String institution; 
  private String degree; 
  private String specialization; 
  private Integer year; 
 
  // The getters and the setters 
} 

The binding of the component will be quite different compared to the usual domain model. To bind all biography attributes to the form components, it is important to access first the getter method of the biography object, then through this we can now access all the setters of its attributes. The following example is a clear picture of the complicated binding:

<form:form commandName="personForm" method="post"> 
  <div class="form_settings"> 
  <!-- insert the page content here --> 
 
    <h2>Update Personal Information</h2> 
    <table style="width:100%; border-spacing:0;"> 
    <tr><td>First Name (*)</td> 
      <td><form:input path="biography.firstName"/></td> 
      <td><form:errors path="biography.firstName" cssStyle="color:        #ff0000"/></td> 
    </tr> 
    <tr><td>Last Name (*)</td> 
      <td><form:input path="biography.lastName"/></td> 
      <td><form:errors path="biography.lastName" cssStyle="color:        #ff0000"/> 
    </tr> 
    <!-- Rest of the script in the sources --> 
  </table> 
 
  <!-- Rest of the code in sources --> 
</form:form> 

The ViewResolver and view configuration

The interface ViewResolver is responsible for the mapping of the view names to the actual view pages. It also provides the view interface, which addresses the request of a view to the view technology. In this PWP project, all of our pages are written in JSP-JSTL so the view interface used is org.springframework.web.servlet.view.JstlView. The project's view resolver is implemented inside the Spring container in this way:

<bean id="viewResolver" 
  class="org.springframework.web.servlet.view.   ResourceBundleViewResolver"> 
  <property name="basename"> 
    <value>config.views</value> 
  </property> 
</bean> 

The views are also declared in the custom property file ./src/config/views.properties:

hello.(class)=org.springframework.web.servlet.view.JstlView 
hello.url=/jsp/hello_world.jsp 
 
index.(class)=org.springframework.web.servlet.view.JstlView 
index.url=/jsp/index.jsp 
 
index_update.(class)=org.springframework.web.servlet.view.JstlView 
index_update.url=/jsp/index_update.jsp 
 
// See the sources 

There are three popular generic ViewResolver implementations that developers always use and these are:

  • InternalResourceViewResolver: This is implemented whenever all the actual view pages are stored inside /WEB-INF/jsp. It has two sets of properties, namely a prefix or suffix that needs to be configured to generate the final view page URL.

            <bean 
              class="org.springframework.web.servlet.view.
                     InternalResourceViewResolver"> 
                <property name="viewClass" 
                value="org.springframework.web.servlet.view.JstlView"/> 
                <property name="prefix"> 
                  <value>/WEB-INF/</value> 
                </property> 
                <property name="suffix"> 
                  <value>.jsp</value> 
                </property> 
            </bean> 
    
  • The prefix indicates the location of the actual views, while the suffix tells the controller the allowed extension of all the actual pages. By default, InternalResourceViewResolver resolves the view names into view objects of type JstlView if the JSTL library is present in the classpath. If the view template is not the default, the viewClass property must be explicitly declared and mapped to other view templates like  org.springframework.web.servlet.view.tiles2.TilesView if tiles are to be used.

  • XmlViewResolver: If you want to declare each individual view-mapping to the actual page in an XML format, then this implementation best fits the project. A sample implementation is shown as follows:

        <bean id="helloWorld" 
          class="org.springframework.web.servlet.view.JstlView"> 
          <property name="url" 
            value="/WEB-INF/helloWorld.jsp" /> 
        </bean> 
  • ResourceBundleViewResolver: This implementation is the most flexible among the three, because the only thing needed here is a property containing all the view mappings. Moreover, one has the capability to combine different view technology in just one project, for the purpose of mixing together presentation layers.

A Spring MVC project can have more than resolvers, given that the order property in all definitions is defined to set order levels of 0 having the highest priority. The following code shows the ordering technique:

<bean 
  class="org.springframework.web.servlet.view.   InternalResourceViewResolver"> 
    <property name="prefix"> 
      <value>/WEB-INF/</value> 
    </property> 
    <property name="suffix"> 
      <value>.jsp</value> 
    </property> 
    <property name="order" value="2" /> 
</bean> 
 
<bean class= "org.springframework.web.servlet.view.XmlViewResolver"> 
  <property name="location"> 
    <value>/WEB-INF/views.xml</value> 
  </property> 
  <property name="order" value="1" /> 
</bean> 
 
<bean class= "org.springframework.web.servlet.view.ResourceBundleViewResolver"> 
  <property name="basename" value="views" /> 
  <property name="order" value="0" /> 
</bean> 

By convention, the InternalResourceViewResolver is always given the least priority because it takes a little time to map the view name directly to the actual pages before all the remaining resolvers. This might give conflict to other mappings if other resolvers are not fast enough in mapping views.

Actual view pages

The view template used in this project is JSP-JSTL, since the view interface used to map to JSP pages is org.springframework.web.servlet.view.JstlView. It is no longer recommended to use scriptlets.

Obviously, the actual view pages use some EL language components like the implicit object sessionScope. EL language is part of the jsp-api.jar libraries, so it is still acceptable to use its components. Moreover, we also used some JSTL tags like <c:out/> and <c:url/>. The tag <c:out/> is always recommended to output values of EL expression ${}, especially in generating reports wherein lots of the data are handled by EL expressions. The attribute escapeXml means that all words that are HTML tags will be captured, and thus they will be rendered on the page as HTML components. But most importantly, redirection implemented inside the view page must use URL rewriting so that when cookies are cut-off, the session data will still be shared by all controllers and views.

Validating Form Data

All form domain objects must be validated using the org.springframework.validation.Validator interface and annotations supported by JSR 303. The validation interface is implemented to create a set of validation rules as per the form view. A sample implementation used in PWP's home content page is shown as follows:

package org.packt.personal.web.portal.validator; 
 
import org.packt.personal.web.portal.model.form.Home; 
import org.springframework.stereotype.Component; 
import org.springframework.validation.Errors; 
import org.springframework.validation.ValidationUtils; 
import org.springframework.validation.Validator; 
 
public class IndexValidator implements Validator { 
  @Override 
  public boolean supports(Class<?> clazz) { 
    return Home.class.equals(clazz); 
  } 
 
  @Override 
  public void validate(Object obj, Errors errors) { 
    Home homeForm = (Home) obj; 
    ValidationUtils.rejectIfEmptyOrWhitespace(errors, 
      "message", "message.empty"); 
    ValidationUtils.rejectIfEmptyOrWhitespace(errors,  
      "quote", "quote.empty"); 
 
    if(homeForm.getMessage().length() > 500) { 
      errors.rejectValue("message", 
        "message.maxlength"); 
    } 
    if(homeForm.getMessage().length() < 50) { 
      errors.rejectValue("message", 
        "message.minlength"); 
    } 
 
    if(homeForm.getQuote().length() > 500) { 
      errors.rejectValue("quote", "quote.maxlength"); 
    } 
    if(homeForm.getQuote().length() < 50) { 
      errors.rejectValue("quote", "quote.minlength"); 
    } 
  } 
 
} 

It validates checks if the user entered a content message and quotes greater than 50, but not greater than 500 alphanumeric. The validator interface has two abstract methods to implement, namely:

  • public boolean supports(): This method checks what type of @ModelAttribute object is being validated. @SessionAttributes are also included in this Boolean method.

  • public void validate(): If the preceding method confirms correctly the @ModelAttribute to be validated, this method will be executed next, dealing with all data values of the domain object.

Validators are components of the Spring MVC project. Spring MVC 4 uses the @Autowired stereotype to call the instance of the validator in the controller class. To enable the validator, the setValidator() method of org.springframework.web.bind .WebDataBinder has to be invoked inside the initBinder() method. To avoid complications, it is recommended to set one validator per initBinder() since we can create more than one initBinder() inside a form controller.

For situations like in PWP where both the @SessionAttributes and @ModelAttribute are utilized by the operations, initBinder() is recommended to be explicitly mapped to the specific attribute for the validator. Otherwise, the following exception shown will be encountered:

SEVERE: Servlet.service() for servlet [pwp] in context with path [/ch01] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Invalid target for Validator [org.packt.personal.web.portal.validator.IndexValidator@5b4ca52d]: undefault] with root cause
java.lang.IllegalStateException: Invalid target for Validator [org.packt.personal.web.portal.validator.IndexValidator@5b4ca52d]: undefault

To retrieve the result of the validation on a @ModelAttribute as per initBinder(), be sure to use the @Validated stereotype with the model argument declared in the submitForm() method. Following is a code that shows how to declare and enable validation in a form controller:

@Autowired 
  private Validator indexValidator; 
 
  @InitBinder("homeForm") 
  public void initBinder(WebDataBinder binder) { 
    binder.setValidator(indexValidator); 
  } 
 
  @RequestMapping(method=RequestMethod.POST) 
  public String submitForm(Model model,    @ModelAttribute("homeForm") @Validated Home homeForm,    BindingResult binding) { 
    model.addAttribute("homeForm", homeForm); 
    String returnVal = "index"; 
    if(binding.hasErrors()) { 
      returnVal = "index_update"; 
    } else { 
      model.addAttribute("homeSess", homeForm); 
      model.addAttribute("statusSess", "undefault"); 
    } 
    return returnVal; 
  } 

Validation using JSR 303

Aside from custom validation using the validator interface, Spring 4.x container supports annotations under the JSR 303 specifications that are applied directly to Java beans, used by the classes to impose specific validation rules. The following EmailController uses @NotNull to check if the two objects are not null, otherwise an error will be detected by the BindingResult.

@Controller 
@RequestMapping("/pwp/contact") 
public class EmailController { 
 
  @NotNull 
  @Autowired 
  private SimpleMailMessage emailTemplate; 
 
  @NotNull 
  @Autowired 
  private JavaMailSender emailSender; 
  // See the sources 
} 

Aside from @NotNull, annotations like @Pattern, and @Size are also widely used in string matching and collection size restrictions, respectively.

Domain data type conversion and filtering

The main purpose of the initBinder() is not purely to validate, but to bind request parameter data to the form domain model. It checks if the request parameter data matches the type of variables in the form-backing object. It provides conversion processes to data in order to avoid type mismatch and other related exceptions. The method has built-in property editors that you can use to check types. Some are custom editors of the type  java.beans.PropertyEditorSupport that check complicated matches with added custom transactions.

In the Personal portal page, there are data that needs to be converted into proper object types like age and birth date.

Remember that request parameter data are all, by default, String objects. The custom property editor can be helpful in converting String data to other types, just to fit in the form-backing object's setter methods. Following is a custom editor for this page:

package org.packt.personal.web.portal.converter; 
import java.beans.PropertyEditorSupport; 
 
public class AgeConverter extends PropertyEditorSupport { 
 
  @Override 
  public void setAsText(String text) throws 
  IllegalArgumentException { 
    Integer age = 0; 
    try { 
      age = Integer.parseInt(text.trim()); 
    } catch(Exception e) { 
      age = 18; 
    } 
    setValue(age); 
  } 
} 
 
package org.packt.personal.web.portal.converter; 
 
import java.beans.PropertyEditorSupport; 
import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
 
public class BirthDateConverter extends PropertyEditorSupport { 
 
  @Override 
  public void setAsText(String text) throws 
  IllegalArgumentException { 
    SimpleDateFormat format = new 
    SimpleDateFormat("mm/yy/dd"); 
    Date birthDate; 
    try { 
      birthDate = format.parse(text.trim()); 
    } catch (ParseException e) { 
      // TODO Auto-generated catch block 
      birthDate = new Date(); 
    } 
    setValue(birthDate); 
  } 
} 

E-mail configuration

The Reach Out page opens an electronic communication between the portal owner and the portal reader or user. The Spring Framework supports e-mail operations with the org.springframework.mail package as the root level package with the following API classes:

  • MailSender: The central interface for sending e-mails is the MailSender interface.

  • SimpleMailMessage: The simple value object encapsulating the properties of a simple mail such as from and to (plus many others) is the class.

  • MailException: The root exception of all e-mail checked exceptions which provide a higher level of abstraction over the lower level mail system exceptions.

  • JavaMailSender: The interface that adds specialized JavaMail features such as MIME message support to its superclass MailSender interface.

  • MimeMessageHelper: A class that comes in pretty handy when dealing with JavaMail messages without using verbose JavaMail APIs.

  • MimeMessagePreparator: A callback interface for the preparation of JavaMail MIME messages.

This project sends three forms of e-mail template, namely the basic text-based e-mail, HTML-based e-mail and template-based e-mail. Following is PWP's way of sending an HTML-based e-mail.

public void sendMailHTML(Email emailForm) { 
  String  fromEmail = emailForm.getSendTo(); 
  String toEmail = emailForm.getSendTo(); 
  String emailSubject = emailForm.getSubject(); 
  String emailBody = emailForm.getMessage(); 
 
  MimeMessage mimeMessage = 
  emailSender.createMimeMessage(); 
  try { 
    MimeMessageHelper helper = new 
    MimeMessageHelper(mimeMessage, true, "utf-8"); 
    mimeMessage.setContent("<i><b>"+emailBody  
    +"</b></i>", "text/html"); 
    helper.setFrom(fromEmail); 
    helper.setTo(toEmail); 
    helper.setSubject(emailSubject); 
 
  } catch (MessagingException e) {} 
  /* 
  uncomment the following lines for attachment 
  FileSystemResource 
  file = new FileSystemResource("sample.jpg"); 
  helper.addAttachment(file.getFilename(), file); 
  */ 
 
  emailSender.send(mimeMessage); 
  System.out.println("Mail sent successfully."); 
 
}