How to do it...

To perform different ways of URL mapping to types of HTTP request handlers, follow these steps:

  1. Since ch03 is a working project already, let us add a simple controller, SimplePatternsController, with three handler methods handling GET and POST transactions inside the org.packt.dissect.mvc.controller package. This simple controller has both the class-level and method-level @RequestMapping annotations, through which the request URLs will be determined based on the class level going down to the URL of its respective handler methods:
@Controller 
@RequestMapping("/simple") 
public class SimplePatternsController { 
 
  @RequestMapping(value="/form_upload_get.html", 
    method=RequestMethod.GET) 
  public String uploadFileFormGet(Model model) { 
    FileUploadForm fileUploadForm = new FileUploadForm(); 
    model.addAttribute("fileUploadForm", fileUploadForm); 
     return "put_form"; 
  } 
  
  @RequestMapping(value="/form_upload_post.html", 
    method=RequestMethod.POST) 
  public String uploadFileFormPost(Model model) { 
    FileUploadForm fileUploadForm = new FileUploadForm(); 
    model.addAttribute("fileUploadForm", fileUploadForm); 
     return "put_form"; 
  } 
  
  @RequestMapping(value="/patterns.html", 
    method=RequestMethod.GET) 
  public String uploadFileForm() { 
    return "simple_patterns"; 
  } 
} 

The handler method uploadFileFormGet() calls the form view for file uploading with the GET transaction mode, while uploadFileFormPost() calls the same form component using the POST method. On the other hand, the method uploadFileForm() is mapped to /patterns.html to serve as the entry point or facade of the application.

  1. The facade page will simply consist of a typical form for the POST method option and hyperlink for the GET method preference. The user can click either of the components to call the upload page:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" 
    pageEncoding="ISO-8859-1"%> 
<%@ taglib prefix="spring"  
   uri="http://www.springframework.org/tags"%> 
<!DOCTYPE html> 
<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> 
<title><spring:message code="simple_patterns_facade" /> 
</title> 
</head> 
<body> 
   <a href="${pageContext.request.contextPath}/simple/ 
         form_upload_get.html">GET Transaction</a> 
   <br/> 
   <form action="${pageContext.request.contextPath}/simple/ 
             form_upload_post.html" method="post"> 
      <input type="submit" value="POST Transaction" /> 
   </form> 
</body> 
</html> 
  1. The put_form view, called by either the POST or GET options of the facade page, serves as the form page where the user is asked to upload a file. Clicking its submit button will generate a PUT request:
<%@ page language="java" contentType="text/html; 
charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 
<%@ taglib prefix="spring" 
uri="http://www.springframework.org/tags" %> 
<%@ taglib prefix="form" 
uri="http://www.springframework.org/tags/form"%> 
<!DOCTYPE html> 
<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html; 
charset=ISO-8859-1"> 
<title><spring:message code="put_title" /></title> 
</head> 
<body> 
<form:form  modelAttribute="uploadFileForm" 
   action="${pageContext.request.contextPath} 
    /simple/upload.html" enctype="multipart/form-data"  
   method="PUT"> 
    <input type="file" name="file"/> 
    <input type="submit" value="Submit"/> 
</form:form> 
</body> 
</html> 
  1. The Spring Form tag library will be used to introduce data binding-aware tags and model form objects to the MVC application and, most of all, will give support and fast delegation to the PUT transactions. After clicking the submit button, the put_form request will be redirected to the method SimplePatternsController, mapped to RequestMethod.PUT. Implement this method as uploadFileSubmit():
@RequestMapping(value="/upload.html", 
method={RequestMethod.PUT, RequestMethod.POST}) 
public String uploadFileSubmit(Model model, 
  @ModelAttribute("fileUploadForm") FileUploadForm 
    fileUploadForm, HttpServletRequest req) { 
    String fileName = 
      fileUploadForm.getFile().getOriginalFilename(); 
    model.addAttribute("transactionType", transactionType); 
    model.addAttribute("fileName", fileName); 
     return "put_result"; 
} 
In order for the PUT transaction to be successfully recognized by the DispatcherServlet, RequesMethod. POST must also be included in @RequestMapping.
  1. The put_result view is implemented by the JSP page below.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> 
<!DOCTYPE > 
<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> 
<title><spring:message code="put_title" /></title> 
</head> 
  <body> 
    <h1>${ transactionType }</h1> 
    <em> ${ fileName } </em> 
  </body> 
</html> 
  1. Now, create another handler method, invoking the DELETE request transaction:
@RequestMapping(value="/delete.html", 
  method={RequestMethod.DELETE, RequestMethod.GET}) 
public String deleteEvent(Model model){  
    String transactionType = "Simple DELETE Transaction"; 
    model.addAttribute("transactionType", transactionType); 
    return "delete"; 
} 
In order for the DELETE transaction to pass through the DispatcherServlet, RequesMethod. GET must also be included in @RequestMapping.
  1. Configuring PUT and DELETE transactions to work in Tomcat 9 is not as easy as their implementation because almost all of the browsers only support POST, GET, and HEAD methods. Using methods other than these will result in HTTP status 405 Request method not supported. There are few solutions which can be helpful in fixing this problem, and these are:
  • Using the framework's org.springframework.web.filter.HiddenHttpMethodFilter, which can auto-generate a _method hidden parameter needed by the framework in the conversion and recognition of PUT and DELETE request modes as valid HTTP methods. We have to add HiddenHttpMethodFilter into our SpringWebInitializer and utilize the Spring Form tag library. This filter will not work without the <form:form> tag component. The following is the FilerRegistration for the HiddenHttpMethodFilter:
@EnableWebMvc 
@ComponentScan(basePackages="org.packt.dissect.mvc") 
@Configuration 
public class SpringWebInitializer implements  
    WebApplicationInitializer { 
 
  // refer to sources 
 
  private void addDispatcherContext(ServletContext      container) { 
  
    // refer to sources 
    FilterRegistration.Dynamic filter =         
       container.addFilter("hiddenmethodfilter",              new HiddenHttpMethodFilter()); 
          filter.addMappingForServletNames(null,                      true, "/*"); 
  } 
 }
  • Customize the org.apache.catalina.filters.CorsFilter and override its cors.allowed.methods property. Most application servers do not support other HTTP methods except GET, POST, HEAD, and OPTIONS. In Tomcat 9, an appropriate solution is to add the CorsFilter into the servlet container SpringWebInitializer, with the addition of PUT and DELETE on its cors.allowed.methods:
private void addDispatcherContext(ServletContext container) { 
  
  // refer to sources 
 
   FilterRegistration.Dynamic corsFilter =   
   container.addFilter("corsFilter", new CorsFilter()); 
   corsFilter.setInitParameter( 
   "cors.allowed.methods","GET, POST, HEAD,  
   OPTIONS, PUT, DELETE"); 
   corsFilter.addMappingForUrlPatterns(null,     
   true, "/*");  
} 
  • CorsFilter is not inherent to Spring APIs but to Tomcat 9 libraries; thus, we need to include the following Maven dependency, given the provided scope:
<dependency> 
    <groupId>org.apache.tomcat</groupId> 
    <artifactId>tomcat-catalina</artifactId> 
    <version>9.0.0.M15</version> 
    <scope>provided</scope> 
</dependency> 
The provided scope means that the JAR files from tomcat-catalina are needed for compilation but not for packaging because of the assumption that it is already provided by the environment.
  1. On the file uploading transaction, the put_form in Step 3 uses the Spring Form tag library to map all request data to a modelAttribute named fileUploadForm. This modelAttribute refers to a form model object or a form backing object that persists all request data for every user transaction. The fileUploadForm is typically a POJO and is written as:
public class FileUploadForm { 
  private MultipartFile file; 
    // getter and setter 
}
In Spring, the uploaded file must be wrapped by an object called org.springframework.web.multipart.MultipartFile, given that the request comes from multipart/form-data. The MultipartFile wraps file contents that come either from memory or temporary disks.
  1. For the PUT transaction to work, the multipart request needs to inject org.springframework.web.multipart.commons.CommonsMultipartResolver to the WebApplicationContext and add org.springframework.web.multipart.support.MultipartFilter to the servlet container. Open the root context definition SpringDispatcherConfig and add the following snippet to the CommonsMultipartResolver settings:
@Bean(name = "multipartResolver") 
public CommonsMultipartResolver getResolver() throws IOException{ 
      CommonsMultipartResolver resolver = 
        new CommonsMultipartResolver(); 
    
      resolver.setMaxUploadSizePerFile(5242880); 
      resolver.setMaxUploadSize(52428807); 
      resolver.setDefaultEncoding("UTF-8"); 
      return resolver; 
} 

Given the preceding details, the CommonsMultipartResolver only accepts files with UTF-8 content-encoding of size 52428807 KB or 5 MB of either single or multiple file uploads. The bean name must be multipartResolver.

  1. For the filter, open SpringWebInitializer and register the MultipartFilter snippet in the addDispatcherContext() method:
FilterRegistration.Dynamic multipartFilter = container.addFilter("multipartFilter", new MultipartFilter()); 
multipartFilter.addMappingForUrlPatterns(null, true, "/*"); 
  1. To wrap up, add the following dependencies for the file uploading PUT request transaction:
<dependency>  
    <groupId>commons-net</groupId>  
    <artifactId>commons-net</artifactId>   
   <version>3.3</version>   
</dependency>   
<dependency> 
    <groupId>commons-fileupload</groupId> 
    <artifactId>commons-fileupload</artifactId> 
    <version>1.3.1</version> 
</dependency> 
<dependency> 
     <groupId>commons-io</groupId> 
     <artifactId>commons-io</artifactId> 
     <version>2.2</version> 
</dependency> 
  1. Save all files. clean, build, and deploy ch03. Run the facade handler uploadFileForm() by calling the class-level URL first (/simple) and then append it to its own URL (/patterns.html). This rule is also true when executing the rest of the handler methods.