5.17. File Upload

Caution

This version is already obsolete. Please check the latest guideline.

5.17.1. Overview

This chapter explains how to upload files.
Files are uploaded using the File Upload functionality supported by Servlet3.0 and classes provided by Spring Web.

Note

In this chapter, File Upload functionality supported by Servlet3.0 is used; hence, Servlet version 3.0 or above is a prerequisite here.

Note

File Upload functionality of Servlet 3.0 may likely result into garbling of multi byte characters of file names or request parameters on some application servesr.

In case of using an application server, wherein problems are likely to occur, using Commons FileUpload can help in avoiding such problems. For settings to use Commons FileUpload, refer to “File upload using Commons FileUpload”.

At the time of version 5.0.1.RELEASE, application servers where this problem is confirmed are as follows:

  • WebLogic 12c
  • JBoss EAP 6

Warning

If implementation of file upload of an application server to be used depends on implementation of Apache Commons FileUpload, security vulnerabilities reported in CVE-2014-0050 may occur. Hence ensure that there are no such vulnerabilities in the application server to be used.

In case of using Tomcat, it is necessary to use version 7.0.52 or above for series 7.0, and version 8.0.3 or above for series 8.0.

5.17.1.1. Basic flow of upload process

Basic flow of uploading files using File Upload functionality supported by Servlet3.0, and classes of Spring Web, is as shown below.

Screen image of single file upload.
Sr. No. Description
(1)
Select and upload the target files.
(2)
Servlet container receives multipart/form-data request and calls org.springframework.web.servlet.DispatcherServlet.
(3)
DispatcherServlet calls the method of org.springframework.web.multipart.support.StandardServletMultipartResolver to enable File Upload functionality of Servlet3.0 in Spring MVC.
StandardServletMultipartResolver generates org.springframework.web.multipart.MultipartFile object that wraps the API (javax.servlet.http.Part) introduced through Servlet 3.0.
(4)
DispatcherServlet calls the processing method of Controller.
DispatcherServlet object created in step (3) binds to the Controller argument or form object.
(5)
Controller calls the method of MultipartFile object and fetches the contents and meta information (file name etc.) of uploaded file.
(6)
MultipartFile calls the method of Part object introduced through Servlet3.0, fetches the contents and meta information (file name etc.) of the uploaded file and returns the same to the Controller.
(7)
Controller calls the Service method and executes upload process.
It passes the contents and meta information (file name etc.) of the file retrieved from MultipartFile object as an argument of Service method.
(8)
Service stores the contents and meta information (file name etc.) of the uploaded file in the file or database.
(9)
DispatcherServlet calls StandardServletMultipartResolver and deletes the temporary file used by File Upload functionality of Servlet3.0.
(10)
StandardServletMultipartResolver calls the method of Part object introduced through Servlet3.0 and deletes the temporary file saved on disk.

Note

Controller performs the process for MultipartFile object of Spring Web; hence implementation which is dependent on the File Upload API provided by Servlet3.0 can be excluded.

5.17.1.2. About classes provided by Spring Web

Classes provided by Spring Web for uploading a file are as follows:

Sr. No.
Class name
Description
org.springframework.web.multipart.
MultipartFile
Interface indicating uploaded file.
It plays a role in abstraction of file objects handled by the File Upload functionality to be used.
org.springframework.web.multipart.support.
StandardMultipartHttpServletRequest$
StandardMultipartFile
MultipartFile class of File Upload functionality introduced through Servlet3.0.
Process is delegated to the Part object introduced through Servlet3.0.
org.springframework.web.multipart.
MultipartResolver
Interface that resolves the analysis method of multipart/form-data request.
It plays a role in generating MultipartFile object corresponding to implementation of File Upload functionality.
org.springframework.web.multipart.support.
StandardServletMultipartResolver
MultipartResolver class for File Upload functionality introduced through Servlet3.0.
org.springframework.web.multipart.support.
MultipartFilter
Class that enables fetching of request parameters in Servlet Filter process, at the time of multipart/form-data request.
If this class is not used, request parameters cannot be fetched in Servlet Filter; hence CSRF Token Check functionality provided by Spring Security does not work correctly.
To be more precise, as CSRF token cannot be fetched, it always throws CSRF token error leading to file upload failure.

Tip

In this guideline, it is a prerequisite to use File Upload functionality implemented from Servlet 3.0. However, Spring Web also provides an implementation class for “Apache Commons FileUpload”. The difference in implementation of upload processes is absorbed by MultipartResolver and MultipartFile objects; hence it does not affect Controller implementation.


5.17.2. How to use

5.17.2.1. Application settings

5.17.2.1.1. Settings to enable Servlet3.0 upload functionality

Perform the following settings to enable upload functionality of Servlet3.0.

  • web.xml
 <web-app xmlns="http://java.sun.com/xml/ns/javaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
     version="3.0"> <!-- (1) (2) -->

     <servlet>
         <servlet-class>
             org.springframework.web.servlet.DispatcherServlet
         </servlet-class>
         <!-- omitted -->
         <multipart-config> <!-- (3) -->
             <max-file-size>5242880</max-file-size> <!-- (4) -->
             <max-request-size>27262976</max-request-size> <!-- (5) -->
              <file-size-threshold>0</file-size-threshold> <!-- (6) -->
         </multipart-config>
     </servlet>

     <!-- omitted -->

 </web-app>
Sr. No. Description
(1)
Specify the XSD file of Servlet3.0 or above in xsi:schemaLocation attribute of <web-app> element.
(2)
Specify version 3.0 or above in the version attribute of <web-app> element.
(3)
Add <multipart-config> element to <servlet> element of the Servlet using the File Upload functionality.
(4)
Specify the maximum file size of 1 upload-permissible file in bytes.
If not specified, -1 (no limit) is set by default.
If it exceeds the specified value, exception, org.springframework.web.multipart.MultipartException occurs.

In the above example, a file size of 5MB is specified.
(5)
Specify the maximum Content-Length value of multipart/form-data request.
If not specified, -1 (no limit) is set by default.
If it exceeds the specified value, exception org.springframework.web.multipart.MultipartException occurs.

Value to be set in this parameter should be calculated by the following formula.

(“maximum file size of 1 file to be uploaded” * “Number of files allowed to be uploaded simultaneously” ) + “Data size of other form fields” + “Meta information size of multipart/form-data request”

In the above example, parameter value of 26MB is specified.
Its breakup is, 25MB (5MB * 5 files) and 1MB (number of bytes of meta information + number of bytes of form fields).
(6)
Specify the threshold value (number of bytes for 1 file) if the contents of uploaded file are to be saved as a temporary file.
If this parameter is not specified explicitly, there are application servers wherein values specified for elements <max-file-size> and <max-request-size> are considered invalid; hence default value (0) is being specified explicitly.

Warning

In order to increase the resistance against Dos attack, max-file-size and max-request-size should be specified without fail.

For Dos attack, refer to Dos attack with respect to upload functionality.

Note

Uploaded file is by default output as temporary file. However, its output can be controlled using the configuration value of <file-size-threshold> element, which is the child element of <multipart-config>.

<!-- omitted -->

<multipart-config>
    <!-- omitted -->
    <file-size-threshold>32768</file-size-threshold> <!-- (7) -->
</multipart-config>

<!-- omitted -->
Sr. No. Description
(7)
Specify the threshold file size (number of bytes of 1 file) if contents of uploaded file are to be saved as a temporary file.
If not specified, 0 is set.
If uploaded file size exceeds the specified value,
it is output as a temporary file to the disk and deleted when the request is completed.

In the above example, 32KB is specified.

Warning

This parameter shows a trade-off relationship as indicated by the following points. Hence, configuration value corresponding to system characteristics should be specified..

  • Increasing the configuration value improves processing performance as, processing gets completed within available memory. However, there is a high possibility that OutOfMemoryError may occur due to Dos attack.
  • If configuration value is reduced, memory utilization can be controlled to the minimum, thereby avoiding the possibility of OutOfMemoryError due to Dos attack etc. However, there is a high possibility of performance degradation since the frequency of disk IO generation is high.

To change output directory of temporary files, specify directory path in <location> element, which is the child element of <multipart-config>.

<!-- omitted -->

<multipart-config>
    <location>/tmp</location> <!-- (8) -->
    <!-- omitted -->
</multipart-config>

<!-- omitted -->
Sr. No. Description
(8)
Specify the directory path for outputting temporary files.
When omitted, they are output to the directory that stores temporary files of application server.

In the above example, /tmp is specified.

Warning

The directory specified in <location> element is the one used by the application server (servlet container) and cannot be accessed from application.

When the files uploaded as application are to be saved as temporary files, they should be output to a directory other than the directory specified in <location> element.

5.17.2.1.2. Settings to enable fetching of request parameters in Servlet Filter processing

Perform the following settings to fetch request parameters in Servlet Filter processing at the time of multipart/form-data request.

  • web.xml
<!-- (1) -->
<filter>
    <filter-name>MultipartFilter</filter-name>
    <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<!-- (2) -->
<filter-mapping>
    <filter-name>MultipartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
Sr. No. Description
(1)
Define MultipartFilter as the Servlet Filter.
(2)
Specify the URL pattern for applying MultipartFilter.

Warning

MultipartFilter needs to be defined before the Servlet Filter that accesses request parameters.

When security measures are to be carried out using Spring Security, it should be defined before springSecurityFilterChain. Further, when request parameters are accessed by a project-specific Servlet Filter, MultipartFilter should be defined before that Servlet Filter.

5.17.2.1.4. Settings for exception handling

Add the exception handling definition of MultipartException which occurs when a request for file or multipart with non-permissible size is sent.

MultipartException is an exception caused due to file size specified by the client; hence it is recommended to handle it as a client error (HTTP response code=4xx).
If exception handling is not added for individual exception, it is eventually treated as system error; hence make sure that it is defined without fail.
Settings for handling MultipartException differ depending upon whether MultipartFilter is used or not.
In case of using MultipartFilter, exception handling is carried out by using the <error-page> functionality of servlet container.
Example of settings is shown below.
  • web.xml
<error-page>
    <!-- (1) -->
    <exception-type>org.springframework.web.multipart.MultipartException</exception-type>
    <!-- (2) -->
    <location>/WEB-INF/views/common/error/fileUploadError.jsp</location>
</error-page>
Sr. No. Description
(1)
Specify MultipartException as the exception class for handling.
(2)
Specify the file displayed when MultipartException occurs.

In the above example, "/WEB-INF/views/common/error/fileUploadError.jsp" is specified.
  • fileUploadError.jsp
<%-- (3) --%>
<% response.setStatus(HttpServletResponse.SC_BAD_REQUEST); %>
<!DOCTYPE html>
<html>

    <!-- omitted -->

</html>
Sr. No. Description
(3)
Set HTTP status code by calling the API of HttpServletResponse.

In the above request, "400" (Bad Request) is set.
When not set explicitly, the HTTP status code is considered as "500" (Internal Server Error).

When not using MultipartFilter, carry out exception handling by using SystemExceptionResolver.
Example of settings is shown below.
  • spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
    <!-- omitted -->
    <property name="exceptionMappings">
        <map>
            <!-- omitted -->
            <!-- (4) -->
            <entry key="MultipartException"
                   value="common/error/fileUploadError" />

        </map>
    </property>
    <property name="statusCodes">
        <map>
            <!-- omitted -->
            <!-- (5) -->
            <entry key="common/error/fileUploadError" value="400" />
        </map>
    </property>
    <!-- omitted -->
</bean>
Sr. No. Description
(4)
In exceptionMappings of SystemExceptionResolver, add the definition for View (JSP) which is displayed when MultipartException occurs.

In the above example, "common/error/fileUploadError" is specified.
(5)
Add the definition of HTTP status code which is received as response when MultipartException occurs.

In the above example, "400" (Bad Request) is specified.
By specifying client error (HTTP response code = 4xx),
the level of log which is output by the class (HandlerExceptionResolverLoggingInterceptor) provided by the exception handling functionality of common library is WARN and not ERROR.

Add exception code settings when setting an exception code for MultipartException.
Exception code is output to the log which is output using log output functionality of common library.
Exception code can also be referred from View (JSP).
For referring to exception code from View (JSP), refer to Method to display system exception code on screen.
  • applicationContext.xml
<bean id="exceptionCodeResolver"
    class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver">
    <property name="exceptionMappings">
        <map>
            <!-- (6) -->
            <entry key="MultipartException" value="e.xx.fw.6001" />
            <!-- omitted -->
        </map>
    </property>
    <property name="defaultExceptionCode" value="e.xx.fw.9001" />
    <!-- omitted -->
</bean>
Sr. No. Description
(6)
In exceptionMappings of SimpleMappingExceptionCodeResolver, add the exception code to be applied when MultipartException occurs.

In the above example, "e.xx.fw.6001" is specified.
When it is not defined individually, exception code specified in defaultExceptionCode is applied.

5.17.2.2. Uploading a single file

The explanation about uploading a single file is given below.

Screen image of single file upload.
There are 2 methods to upload a single file. One is by binding org.springframework.web.multipart.MultipartFile object to the form object and the other is by receiving it directly as Controller argument. However, this guideline recommends the first method wherein it is received after it is bound with the form object.
The reason for this being, single field check of the uploaded file can be performed using Bean Validation.

How to receive a single file by binding it to form object is explained below.

5.17.2.2.1. Implementing form

public class FileUploadForm implements Serializable {

    // omitted

    private MultipartFile file; // (1)

    @NotNull
    @Size(min = 0, max = 100)
    private String description;

    // omitted getter/setter methods.

}
Sr. No. Description
(1)
Define properties of org.springframework.web.multipart.MultipartFile in form object.

5.17.2.2.2. Implementing JSP

<form:form
  action="${pageContext.request.contextPath}/article/upload" method="post"
  modelAttribute="fileUploadForm" enctype="multipart/form-data"> <!-- (1) (2) -->
  <table>
    <tr>
      <th width="35%">File to upload</th>
      <td width="65%">
        <form:input type="file" path="file" /> <!-- (3) -->
        <form:errors path="file" />
      </td>
    </tr>
    <tr>
      <th width="35%">Description</th>
      <td width="65%">
        <form:input path="description" />
        <form:errors  path="description" />
      </td>
    </tr>
    <tr>
      <td>&nbsp;</td>
      <td><form:button>Upload</form:button></td>
    </tr>
  </table>
</form:form>
Sr. No. Description
(1)
Specify "multipart/form-data" in the enctype attribute of <form:form> element.
(2)
Specify attribute name of form object in the modelAttribute of <form:form> element.
In the above example, "fileUploadForm" is specified.
(3)
Specify "file" in type attribute of <form:input> element and specify MultipartFile property name in path attribute.
In the above example, the uploaded file is stored in "file" property of FileUploadForm object.

5.17.2.2.3. Implementing Controller

@RequestMapping("article")
@Controller
public class ArticleController {

    @Value("${upload.allowableFileSize}")
    private int uploadAllowableFileSize;

    // omitted

    // (1)
    @ModelAttribute
    public FileUploadForm setFileUploadForm() {
        return new FileUploadForm();
    }

    // (2)
    @RequestMapping(value = "upload", method = RequestMethod.GET, params = "form")
    public String uploadForm() {
        return "article/uploadForm";
    }

    // (3)
    @RequestMapping(value = "upload", method = RequestMethod.POST)
    public String upload(@Validated FileUploadForm form,
            BindingResult result, RedirectAttributes redirectAttributes) {

        if (result.hasErrors()) {
            return "article/uploadForm";
        }

        MultipartFile uploadFile = form.getFile();

        // (4)
        if (!StringUtils.hasLength(uploadFile.getOriginalFilename())) {
            result.rejectValue(uploadFile.getName(), "e.xx.at.6002");
            return "article/uploadForm";
        }

        // (5)
        if (uploadFile.isEmpty()) {
            result.rejectValue(uploadFile.getName(), "e.xx.at.6003");
            return "article/uploadForm";
        }

        // (6)
        if (uploadAllowableFileSize < uploadFile.getSize()) {
            result.rejectValue(uploadFile.getName(), "e.xx.at.6004",
                    new Object[] { uploadAllowableFileSize }, null);
            return "article/uploadForm";
        }

        // (7)
        // omit processing of upload.

        // (8)
        redirectAttributes.addFlashAttribute(ResultMessages.success().add(
                "i.xx.at.0001"));

        // (9)
        return "redirect:/article/upload?complete";
    }

    @RequestMapping(value = "upload", method = RequestMethod.GET, params = "complete")
    public String uploadComplete() {
        return "article/uploadComplete";
    }

        // omitted

}
Sr. No. Description
(1)
Method of storing the form object for file upload in Model.
In the above example, the attribute name for storing form object in Model is "fileUploadForm".
(2)
Processing method for displaying upload screen.
(3)
Processing method for uploading files.
(4)
It is checked whether the files for upload are selected.
To check if the files are selected, call MultipartFile#getOriginalFilename method and decide on the basis of whether file name is specified or not.
In the above example, input validation error is thrown if the files are not selected.
(5)
It is checked whether an empty file is selected.
To check if the selected file is not empty, call MultipartFile#isEmpty method to check for presence of contents.
In the above example, input validation error is thrown if an empty file is selected.
(6)
It is checked whether the file size is within allowable range.
To check the size of selected file, call MultipartFile#getSize method and check whether the size is within the allowable range.
In the above example, input validation error is thrown if the file size exceeds the allowable range.
(7)
Implement upload process.
The above example does not cover any specific implementation; however process to store the file on a shared disk or database is performed.
(8)
As per the requirement, the processing result message notifying about successful upload is stored.
(9)
Once upload is complete, redirect to upload completion screen.

Note

Preventing duplicate upload

When uploading files, it is recommended to perform transaction token check and screen transition based on PRG pattern. With this, upload of same files caused due to double submission can be prevented.

For more details on how to prevent double submission, refer to Double Submit Protection.

Note

About MultipartFile

Methods to operate the uploaded file are provided in MultipartFile. For details on using each method, refer to `JavaDoc <http://docs.spring.io/spring/docs/4.1.7.RELEASE/javadoc-api/org/springframework/web/multipart/MultipartFile.html> of MultipartFile class `_.

5.17.2.3. Bean Validation of file upload

In the above implementation example, uploaded file is validated as a Controller process. However, here the uploaded file is validated using Bean Validation.
For validation details, refer to Input Validation.

Note

It is recommended to use Bean Validation since this makes maintenance of Controller processes easier.

5.17.2.3.1. Implementing validation to verify that the file is selected

// (1)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UploadFileRequiredValidator.class)
public @interface UploadFileRequired {
            String message() default "{com.examples.upload.UploadFileRequired.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        UploadFileRequired[] value();
    }

}
// (2)
public class UploadFileRequiredValidator implements
    ConstraintValidator<UploadFileRequired, MultipartFile> {

    @Override
    public void initialize(UploadFileRequired constraint) {
    }

    @Override
    public boolean isValid(MultipartFile multipartFile,
        ConstraintValidatorContext context) {
        return multipartFile != null &&
            StringUtils.hasLength(multipartFile.getOriginalFilename());
    }

}
Sr. No. Description
(1)
Create annotation to verify that the file is selected.
(2)
Create implementation class to verify that the file is selected.

5.17.2.3.2. Implementing validation to verify that the file is not empty

// (3)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UploadFileNotEmptyValidator.class)
public @interface UploadFileNotEmpty {
    String message() default "{com.examples.upload.UploadFileNotEmpty.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        UploadFileNotEmpty[] value();
    }

}
// (4)
public class UploadFileNotEmptyValidator implements
    ConstraintValidator<UploadFileNotEmpty, MultipartFile> {

    @Override
    public void initialize(UploadFileNotEmpty constraint) {
    }

    @Override
    public boolean isValid(MultipartFile multipartFile,
        ConstraintValidatorContext context) {
        if (multipartFile == null ||
            !StringUtils.hasLength(multipartFile.getOriginalFilename())) {
            return true;
        }
        return !multipartFile.isEmpty();
    }

}
Sr. No. Description
(3)
Create annotation to verify that the file is not empty.
(4)
Create implementation class to verify that the file is not empty.

5.17.2.3.3. Implementing validation to verify that file size is within allowable range

// (5)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UploadFileMaxSizeValidator.class)
public @interface UploadFileMaxSize {
    String message() default "{com.examples.upload.UploadFileMaxSize.message}";
    long value() default (1024 * 1024);
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        UploadFileMaxSize[] value();
    }

}
// (6)
public class UploadFileMaxSizeValidator implements
    ConstraintValidator<UploadFileMaxSize, MultipartFile> {

    private UploadFileMaxSize constraint;

    @Override
    public void initialize(UploadFileMaxSize constraint) {
        this.constraint = constraint;
    }

    @Override
    public boolean isValid(MultipartFile multipartFile,
        ConstraintValidatorContext context) {
        if (constraint.value() < 0 || multipartFile == null) {
            return true;
        }
        return multipartFile.getSize() <= constraint.value();
    }

}
Sr. No. Description
(5)
Create annotation to verify that the file size is within allowable range.
(6)
Create implementation class to verify that the file size is within allowable range.

5.17.2.3.4. Implementing form

public class FileUploadForm implements Serializable {

    // omitted

    // (7)
    @UploadFileRequired
    @UploadFileNotEmpty
    @UploadFileMaxSize
    private MultipartFile file;

    @NotNull
    @Size(min = 0, max = 100)
    private String description;

    // omitted getter/setter methods.

}
Sr. No. Description
(7)
Assign annotation to MultipartFile field for validating uploaded file.

5.17.2.3.5. Implementing Controller

@RequestMapping(value = "upload", method = RequestMethod.POST)
public String uploadFile(@Validated FileUploadForm form,
        BindingResult result, RedirectAttributes redirectAttributes) {

    // (8)
    if (result.hasErrors()) {
        return "article/uploadForm";
    }

    MultipartFile uploadFile = form.getFile();

    // omit processing of upload.

    redirectAttributes.addFlashAttribute(ResultMessages.success().add(
            "i.xx.at.0001"));

    return "redirect:/article/upload";
}
Sr. No. Description
(8)
Validation result of uploaded file is stored in BindingResult.

5.17.2.4. Uploading multiple files

This section explains about simultaneously uploading multiple files.

Screen image of multiple file upload.

In order to upload multiple files simultaneously, it is necessary to receive org.springframework.web.multipart.MultipartFile object by binding it to the form object.

The explanation that has already been covered under single file upload has been omitted to avoid duplication.

5.17.2.4.1. Implementing form

// (1)
public class FileUploadForm implements Serializable {

    // omitted

    @UploadFileRequired
    @UploadFileNotEmpty
    @UploadFileMaxSize
    private MultipartFile file;

    @NotNull
    @Size(min = 0, max = 100)
    private String description;

    // omitted getter/setter methods.

}
public class FilesUploadForm implements Serializable {

    // omitted

    @Valid // (2)
    private List<FileUploadForm> fileUploadForms; // (3)

    // omitted getter/setter methods.

}
Sr. No. Description
(1)
Class that maintains the information of each file (uploaded file itself and related form fields).
In the above example, form object that was originally created to explain about single file upload, is re-used.
(2)
To carry out input validation through Bean Validation for the object maintained in list, assign @Valid annotation.
(3)
Define the object that maintains information of each file (uploaded file itself and related form fields) as List property.

Note

When only files are to be uploaded, MultipartFile object can also be defined as List property; however, for input validation of uploaded files using Bean Validation, there is better compatibility if the object that maintains information of each file, is defined as List property.

5.17.2.4.2. Implementing JSP

<form:form
  action="${pageContext.request.contextPath}/article/uploadFiles" method="post"
  modelAttribute="filesUploadForm" enctype="multipart/form-data">
  <table>
    <tr>
      <th width="35%">File to upload</th>
      <td width="65%">
        <form:input type="file" path="fileUploadForms[0].file" /> <!-- (1) -->
        <form:errors path="fileUploadForms[0].file" />
      </td>
    </tr>
    <tr>
      <th width="35%">Description</th>
      <td width="65%">
        <form:input path="fileUploadForms[0].description" />
        <form:errors  path="fileUploadForms[0].description" />
      </td>
    </tr>
  </table>
  <table>
    <tr>
      <th width="35%">File to upload</th>
      <td width="65%">
        <form:input type="file" path="fileUploadForms[1].file" /> <!-- (1) -->
        <form:errors path="fileUploadForms[1].file" />
      </td>
    </tr>
    <tr>
      <th width="35%">Description</th>
      <td width="65%">
        <form:input path="fileUploadForms[1].description" />
        <form:errors path="fileUploadForms[1].description" />
      </td>
    </tr>
  </table>
  <div>
    <form:button>Upload</form:button>
  </div>
</form:form>
Sr. No. Description
(1)
Specify the binding position of the uploaded file in List.
Specify the binding position within List in []. Start position begins with 0.

5.17.2.4.3. Implementing Controller

@RequestMapping(value = "uploadFiles", method = RequestMethod.POST)
public String uploadFiles(@Validated FilesUploadForm form,
        BindingResult result, RedirectAttributes redirectAttributes) {

    if (result.hasErrors()) {
        return "article/uploadForm";
    }

    // (1)
    for (FileUploadForm fileUploadForm : form.getFileUploadForms()) {

        MultipartFile uploadFile = fileUploadForm.getFile();

        // omit processing of upload.

    }

    redirectAttributes.addFlashAttribute(ResultMessages.success().add(
            "i.xx.at.0001"));

    return "redirect:/article/upload?complete";
}
:header-rows: 1 :widths: 10 90
Sr. No. Description
(1)
Fetch MultipartFile from the object that maintains information of each file (uploaded file itself and related form fields) and implement upload process.
The above example does not cover any specific implementation; however process to store the file on a shared disk or database is performed.

5.17.2.5. Uploading multiple files using the “multiple” attribute of HTML5

The method to simultaneously upload multiple files using “multiple” attribute of input tag supported by HTML5, is explained below.

Screen image of multiple file upload(html5).

The explanation that has already been covered under single file upload and multiple file upload has been omitted.

5.17.2.5.1. Implementing form

When uploading multiple files simultaneously using “multiple” attribute of HTML5 input tag, it is necessary to receive collection of org.springframework.web.multipart.MultipartFile object by binding it to form object.

// (1)
public class FilesUploadForm implements Serializable {

    // omitted

    // (2)
    @UploadFileNotEmpty
    private List<MultipartFile> files;

    // omitted getter/setter methods.

}
Sr. No. Description
(1)
Form object that maintains the multiple uploaded files.
(2)
Declare MultipartFile class as list.
In the above example, the annotation to verify that the file is not empty, is specified as input validation.
Principally, a file size check or other mandatory checks are also required; however, they have been omitted in the above example.

5.17.2.5.2. Implementing Validator

When carrying out input validation for multiple MultipartFile objects stored in collection, it is necessary to implement Validator for Collection.

The section explains about creating Validator for Collection using the Validator created for single file.

// (1)
public class UploadFileNotEmptyForCollectionValidator implements
    ConstraintValidator<UploadFileNotEmpty, Collection<MultipartFile>> {

    // (2)
    private final UploadFileNotEmptyValidator validator =
        new UploadFileNotEmptyValidator();

    // (3)
    @Override
    public void initialize(UploadFileNotEmpty constraintAnnotation) {
        validator.initialize(constraintAnnotation);
    }

    // (4)
    @Override
    public boolean isValid(Collection<MultipartFile> values,
            ConstraintValidatorContext context) {
        for (MultipartFile file : values) {
            if (!validator.isValid(file, context)) {
                return false;
            }
        }
        return true;
    }

}
Sr. No. Description
(1)
Class for performing implementation to verify that none of the files is empty.
Specify Collection<MultipartFile> as the type of value to be verified.
(2)
In order to delegate the actual process to a Validator for single file, create an instance for that Validator.
(3)
Initialize the Validator.
In the above example, Validator for single file that implements the actual process is initialized.
(4)
Verify that none of the file is empty.
In the above example, each file is verified by calling the method of Validator for single file.
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy =
    {UploadFileNotEmptyValidator.class,
     UploadFileNotEmptyForCollectionValidator.class}) // (5)
public @interface UploadFileNotEmpty {

    // omitted

}
Sr. No. Description
(5)
Add the Validator class that carries out checks with respect to multiple files, to the annotation used for verification.
Specify the class created in step (1) in the “validatedBy” attribute of @Constraint annotation.
With this, the class created in step (1) is executed when validating the property with @UploadFileNotEmpty annotation.

5.17.2.5.3. Implementing JSP

<form:form
  action="${pageContext.request.contextPath}/article/uploadFiles" method="post"
  modelAttribute="filesUploadForm2" enctype="multipart/form-data">
  <table>
    <tr>
      <th width="35%">File to upload</th>
      <td width="65%">
        <form:input type="file" path="files" multiple="multiple" /> <!-- (1) -->
        <form:errors path="files" />
      </td>
    </tr>
  </table>
  <div>
    <form:button>Upload</form:button>
  </div>
</form:form>
Sr. No. Description
(1)
In “path” attribute, specify “multiple” attribute by indicating property name of form object.
By specifying “multiple” attribute, multiple files can be selected and uploaded using browser supporting HTML5.

5.17.2.5.4. Implementing Controller

@RequestMapping(value = "uploadFiles", method = RequestMethod.POST)
public String uploadFiles(@Validated FilesUploadForm form,
        BindingResult result, RedirectAttributes redirectAttributes) {
    if (result.hasErrors()) {
        return "article/uploadForm";
    }

    // (1)
    for (MultipartFile file : form.getFiles()) {

        // omit processing of upload.

    }

    redirectAttributes.addFlashAttribute(ResultMessages.success().add(
            "i.xx.at.0001"));

    return "redirect:/article/upload?complete";
}
Sr. No. Description
(1)
Implement upload process by fetching the list which stores MultipartFile objects from form object.
The above example does not cover any specific implementation; however process to store the file on a shared disk or database is performed.

5.17.2.6. Temporary upload

Temporary upload is required when a file is to be uploaded midway through screen transitions like upload result confirmation screen etc.

Note

Contents of file stored in MultipartFile object may be deleted once the upload request is completed. Therefore, when the file contents are to be handled across requests, these contents and meta information (file name etc.) maintained in MultipartFile object need to be saved in a file or form.

The contents of file stored in MultipartFile object are deleted when step (3) of the following processing flow is completed.

Processing flow of temporary upload.
Sr. No. Description
(1)
On Input Screen, select the file to be uploaded and send a request for displaying Confirm Screen.
(2)
Controller temporarily saves contents of uploaded file in the temporary directory for application.
(3)
Controller returns View name of Confirm Screen and then displays the Confirm Screen.
(4)
On Confirm screen, send a request for executing the process.
(5)
Controller calls Service method and executes process.
(6)
Service moves the temporary file saved in temporary directory to this directory or database.
(7)
Controller returns the View name which is required to display Complete Screen and then displays the Complete Screen.

Note

Temporary upload process is the responsibility of application layer; hence it is executed by Controller or Helper class.

5.17.2.6.1. Implementing Controller

Example for temporarily saving the uploaded file in a temporary directory, is shown below.

@Component
public class UploadHelper {

    // (2)
    @Value("${app.upload.temporaryDirectory}")
    private File uploadTemporaryDirectory;

    // (1)
    public String saveTemporaryFile(MultipartFile multipartFile)
        throws IOException {

        String uploadTemporaryFileId = UUID.randomUUID().toString();
        File uploadTemporaryFile =
            new File(uploadTemporaryDirectory, uploadTemporaryFileId);

        // (2)
        FileUtils.copyInputStreamToFile(multipartFile.getInputStream(),
                uploadTemporaryFile);

        return uploadTemporaryFileId;
    }

}
Sr. No. Description
(1)
Create a method for executing temporary upload in Helper class.
When there are multiple processes that perform file upload, it is recommended to have a common temporary upload process by creating a common Helper method.
(2)
Save the uploaded file as a temporary file.
In the above example, contents of uploaded file are saved to a file by calling copyInputStreamToFile method of org.apache.commons.io.FileUtils class.
  // omitted

  @Inject
  UploadHelper uploadHelper;

  @RequestMapping(value = "upload", method = RequestMethod.POST, params = "confirm")
  public String uploadConfirm(@Validated FileUploadForm form,
          BindingResult result) throws IOException {

      if (result.hasErrors()) {
          return "article/uploadForm";
      }

      // (3)
      String uploadTemporaryFileId = uploadHelper.saveTemporaryFile(form
              .getFile());

      // (4)
      form.setUploadTemporaryFileId(uploadTemporaryFileId);
      form.setFileName(form.getFile().getOriginalFilename());

      return "article/uploadConfirm";
  }

.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
 :header-rows: 1
 :widths: 10 90

 * - Sr. No.
   - Description
 * - | (3)
   - | Call the Helper method to temporarily save the uploaded file.
     | In the above example, ID by which the temporarily saved file is identified, is returned as the return value of Helper method.
 * - | (4)
   - | Save the meta information of uploaded file (ID by which the file is identified, file name etc.) in form object.
     | In the above example, name of the uploaded file and ID by which the temporarily saved file is identified, are stored in form object.

Note

Directory of temporary directories should be fetched from external properties as it may differ with the environment in which the application is deployed. For details on external properties, refer to Properties Management.

Warning

In the above example, it is a file saved temporarily on the local disk of application server. However, when the application server is clustered, it needs to be saved in the database or on a shared disk. As a result, it is necessary to design a storage destination by considering even the non-functional requirements.

Transaction management is necessary in case of saving the file to the database. As a result, the process to save it to the database will be delegated to Service method.


5.17.3. How to extend

5.17.3.1. Housekeeping of unnecessary files at the time of temporary upload

When uploading files using the temporary upload method, there is a possibility of unnecessary files piling up in temporary directory.
The cases are as follows:
  • When there is interruption in screen operations after temporary upload
  • When system error occurs during the screen operations after temporary upload
  • When server stops during the screen operations after temporary upload etc …

Warning

A mechanism should be provided to delete unnecessary files as the disk may run out of space if such files are left to pile up.

This guideline explains about deleting unnecessary files using the “Task Scheduler” functionality provided by Spring Framework. For details on “Task Scheduler”, refer to the official website “Task Execution and Scheduling”.

Note

Although this guideline explains about how to use “Task Scheduler” functionality provided by Spring Framework; its usage is not mandatory. In an actual project, the infrastructure team may provide batch application (Shell application) to delete unnecessary files. In such cases, it is recommended to delete unnecessary files using the batch application created by infrastructure team.

5.17.3.1.1. Implementing component class to delete unnecessary files

Implement a component class to delete unnecessary files.

package com.examples.common.upload;

import java.io.File;
import java.util.Collection;
import java.util.Date;

import javax.inject.Inject;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.springframework.beans.factory.annotation.Value;
import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory;

    // (1)
    public class UnnecessaryFilesCleaner {

    @Inject
    JodaTimeDateFactory dateFactory;

    @Value("${app.upload.temporaryFileSavedPeriodMinutes}")
    private int savedPeriodMinutes;

    @Value("${app.upload.temporaryDirectory}")
    private File targetDirectory;

    // (2)
    public void cleanup() {

        // calculate cutoff date.
        Date cutoffDate = dateFactory.newDateTime().minusMinutes(
                savedPeriodMinutes).toDate();

        // collect target files.
        IOFileFilter fileFilter = FileFilterUtils.ageFileFilter(cutoffDate);
        Collection<File> targetFiles = FileUtils.listFiles(targetDirectory,
                fileFilter, null);

        if (targetFiles.isEmpty()) {
            return;
        }

        // delete files.
        for (File targetFile : targetFiles) {
            FileUtils.deleteQuietly(targetFile);
        }

    }

}
Sr. No. Description
(1)
Create component class to delete unnecessary files.
(2)
Implement the method to delete unnecessary files.
In the above example, the files that have not been updated for a certain period of time from the last update, are treated as unnecessary files and are deleted.

Note

Directory path in which files to be deleted are stored or the time criteria for deletion etc. may differ depending upon the environment in which application is to be deployed. Hence they should be fetched from external properties. For details on external properties, refer to Properties Management.

5.17.3.1.2. Scheduling settings of the process for deleting unnecessary files

Carry out bean registration and task schedule settings for the POJO class that deletes unnecessary files.

  • applicationContext.xml
<!-- omitted -->

<!-- (3) -->
<bean id="uploadTemporaryFileCleaner"
    class="com.examples.common.upload.UnnecessaryFilesCleaner" />

<!-- (4) -->
<task:scheduler id="fileCleanupTaskScheduler" />

<!-- (5) -->
<task:scheduled-tasks scheduler="fileCleanupTaskScheduler">
    <!-- (6)(7)(8) -->
    <task:scheduled ref="uploadTemporaryFileCleaner"
                    method="cleanup"
                    cron="${app.upload.temporaryFilesCleaner.cron}"/>
</task:scheduled-tasks>

<!-- omitted -->
Sr. No. Description
(3)
POJO class that deletes unnecessary files should be registered in bean.
In the above example, it is registered with "uploadTemporaryFileCleaner" ID.
(4)
Register the bean for task scheduler that executes the process to delete unnecessary files.
In the above example, as pool-size attribute is omitted, this task scheduler executes the task in a single thread .
When multiple tasks need to be executed simultaneously, some number should be specified in pool-size attribute.
(5)
Add the task to the task scheduler that deletes unnecessary files.
In the above example, task is added to the task scheduler for which bean is registered in step (4).
(6)
In ref attribute, specify the bean that executes the process of deleting unnecessary files.
In the above example, the bean registered in step (3) is specified.
(7)
In method attribute, specify the name of method executing the process of deleting unnecessary files.
In the above example, cleanup method of bean registered in step (3) is specified.
(8)
In cron attribute, specify execution time of the process to delete unnecessary files.
In the above example, cron definition is fetched from external properties.

Note

Specify the configuration value of cron attribute in “seconds minutes hour month year day” format.

Example:

  • 0 */15 * * * * : Executed in 0 minute, 15 minutes, 30 minutes and 45 minutes every hour.
  • 0 0 * * * * : Executed in 0 minute every hour.
  • 0 0 9-17 * * MON-FRI : Executed in 0 minute every hour from 9:00~17:00 on weekdays.

For details on specified value of cron, refer to CronSequenceGenerator - JavaDoc.

Execution time should be fetched from external properties as it may differ depending on the environment in which the application is to be deployed. For details on external properties, refer to Properties Management.

Tip

In the above example, cron is used as a trigger for executing tasks. However, other triggers namely fixed-delay and fixed-rate are also set by default and should be selectively used as per requirement.

When the default triggers do not satisfy the requirements, an independent trigger can be set by specifying the bean implementing org.springframework.scheduling.Trigger in trigger attribute.


5.17.4. Appendix

5.17.4.2. File upload using Commons FileUpload

If File Upload functionality of Servlet 3.0 is only used partially on Application Server, it may likely result into garbling of multi byte characters of file names or request parameters.

For example: If File Upload functionality of Servlet 3.0 is used on WebLogic (verification version 12.1.3), it has been confirmed that multi byte characters of fields to be sent along with file are garbled. Although it seems that there is a problem at the Application Server side, it is not possible to send the file and multi byte characters simultaneously unless the said problem is fixed.

This problem can be avoided using Commons FileUpload. Therefore, this guideline describes about file upload using Commons FileUpload as a temporary measure till the application server gets modified.

Perform the following settings when using Commons FileUpload.


xxx-web/pom.xml

<!-- (1) -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
</dependency>
Sr. No.
Description
(1)
Add dependency to commons-fileupload.
No need to specify the version in pom.xml as it is defined depending on Spring IO Platform.

Warning

In case of using Apache Commons FileUpload, security vulnerabilities reported in CVE-2014-0050 are likely to occur. Confirm that there are no vulnerabilities in the version of Apache Commons FileUpload to be used.

When using Apache Commons FileUpload, version 1.3.1 or above should be used.

Further, if a version stored in Spring IO Platform is used, the vulnerabilities reported in CVE-2014-0050 do not occur.


xxx-web/src/main/resources/META-INF/spring/applicationContext.xml

<!-- (1) -->
<bean id="filterMultipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="10240000" /><!-- (2) -->
</bean>

<!-- ... -->
Sr. No.
Description
(1)
Perform bean definition of CommonsMultipartResolver with MultipartResolver implemented using Commons FileUpload.
Specify "filterMultipartResolver" in bean ID.
(2)
Set maximum size allowed in file upload.
In case of Commons FileUpload, it should be noted that the maximum value is the entire size of request including header.
Moreover, as the default value is -1 (unlimited), make sure to set a value. For other properties, refer to JavaDoc.

Warning

In case of using Commons Fileupload, MultipartResolver should be defined in applicationContext.xml and not in spring-mvc.xml. It should be deleted if defined in spring-mvc.xml.


xxx-web/src/main/webapp/WEB-INF/web.xml

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <servlet>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- omitted -->
        <!-- (1) -->
        <!-- <multipart-config>...</multipart-config> -->
    </servlet>

    <!-- (2) -->
    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- omitted -->

</web-app>
Sr.No. Description
(1)
When using Commons FileUpload, an upload function of Servlet 3.0 should be disabled.
If <multipart-config> element is present in DispatcherServlet definition, make sure to delete the same.
(2)
When using Commons Fileupload, MultipartFilter should be defined to enable CSRF measures.
MultipartFiltermapping should be defined before defining springSecurityFilterChain (Servlet Filter of Spring Security).

Tip

MultipartFilteris a mechanism to perform the file upload process by fetching MultipartResolverregistered with bean ID "filterMultipartResolver" from DI container (applicationContext.xml).