Thursday, 3 December 2015

Spring 4 MultipartFile and Angular File Upload with Spring Security

Hi,

In Angular file upload is a bit tricky as ng-model doesn't support file and in JSON we can't send file.
In Spring with File Multipart accepting with other values is complex to implement.

It was time consuming to find approaches to Upload file from Angular and Accepting as File MultiPart in Spring.

Angular side

1. Create a directive to fetch the file.
2. Inject all required things to the Controller.

Here in Directive restrict type 'A' is used. You can change with any type if required.

I have used JSP to ease with csrf tokens.

 <!--  
 Author : Ramakrishna Panni  
 name: fileUpload.jsp  
 details: To test file upload with a File,Pic and a String   
 -->  
 <%@ 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"%>  
 <html>  
 <head>  
      <meta name="_csrf" content="${_csrf.token}"/>  
      <!-- default header name is X-CSRF-TOKEN -->  
      <meta name="_csrf_header" content="${_csrf.headerName}"/>  
  <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.3.min.js"></script>  
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.24/angular.min.js"></script>  
 </head>  
 <body ng-app='fileUpload' ng-controller='UploadController'>  
      <form enctype="multipart/form-data"  
           ng-submit="submitForm()">  
           File to upload: <input type="file" file-model="cvBlob"><br /> Name: <input  
                type="text" name="name" ng-model="result.success"><br /> <br />   
           Pic to Upload:     <input type="file" file-model="picBlob"><br /> <br /><input type="submit"  
                value="Upload"> Press here to upload the file!  
                <input type="hidden" id="csrf" name="${_csrf.parameterName}" value="${_csrf.token}" />  
      </form>  
      <script type="text/javascript">  
      var upload = angular.module('fileUpload', []);  
      upload.controller('UploadController', ['$scope', '$window','$http', function($scope, $window,$http,fileUpload) {  
            $scope.result = {};  
            var token = $("meta[name='_csrf']").attr("content");  
         var header = $("meta[name='_csrf_header']").attr("content");  
            $scope.submitForm = function(){  
                 var pic = $scope.picBlob;  
                 var cv = $scope.cvBlob;  
                 var uploadUrl = "/upload?_csrf="+token  
                 console.log(pic);  
                 var fd = new FormData();  
                 fd.append('pic', pic);  
               fd.append('cv', cv);  
               fd.append('result',JSON.stringify($scope.result));  //You should make it to String while sending
               $http.post(uploadUrl, fd, {  
                    transformRequest: function (data, headersGetterFunction) {  
                   return data;  
                  },  // To take Http Request and Headers and making it Serialized, without serizlized object Spring will give Media Not Supported error
                    headers: {'Content-Type': undefined}  
               }) .success(function(data){  
                    if (data.error === "") {  
                       // Showing errors.  
                            $scope.success = data.success;  
                      } else {  
                           $scope.error = data.error;  
                      }   
               })  
               .error(function(){  
               });       
            };  
       }]);  
      upload.directive('fileModel', ['$parse', function ($parse) {  
             return {  
               restrict: 'A',  
               link: function(scope, element, attrs) {  
                 var model = $parse(attrs.fileModel);  
                 var modelSetter = model.assign;  
                 element.bind('change', function(){  
                   scope.$apply(function(){  
                     modelSetter(scope, element[0].files[0]);  
                   });  
                 });  
               }  
             };  
           }]);  
      </script>  
 </body>  
 </html>  

Points to look on
  • Append every data in FormData object using property tags.
  • Stringify  JSON  append in FormData
  • Transform the request with headers as Serialized 
  • Make Content Undefined as Angular will convert it to right one

Spring Side

  1. Create Bean of  "MultipartResolver"
  2. Add Multipart Filter in "AbstractSecurityWebApplicationInitializer"
JAR's needed commons-io,commons-fileupload and jackson.

MultipartResolver Bean

In this you can set the max upload size,encoding, lazy resolve,temp directory and others.
Define in Application configuration class.
 @Bean(name = "filterMultipartResolver")  
       public CommonsMultipartResolver commonsMultipartResolver(){  
            CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();  
            commonsMultipartResolver.setDefaultEncoding("utf-8");  
            commonsMultipartResolver.setMaxUploadSize(50000000);  
            return commonsMultipartResolver;  
       }  

MultipartFilter


Define in Application configuration class.
 protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {  
        insertFilters(servletContext, new MultipartFilter());  
       }  

Controller Class


 package com.mindfiresolutions.springmaven.controller;  
 import java.io.BufferedOutputStream;  
 import java.io.File;  
 import java.io.FileOutputStream;  
 import org.codehaus.jackson.map.ObjectMapper;  
 import org.springframework.stereotype.Controller;  
 import org.springframework.web.bind.annotation.RequestMapping;  
 import org.springframework.web.bind.annotation.RequestMethod;  
 import org.springframework.web.bind.annotation.RequestParam;  
 import org.springframework.web.bind.annotation.ResponseBody;  
 import org.springframework.web.multipart.MultipartFile;  
 import com.mindfiresolutions.springmaven.models.Result;  
 /**  
  * Author: Ramakrishna Panni  
  * Class: FileUploadController  
  * Details: Created to handle Sample file Upload  
  */  
 @Controller  
 public class FileUploadController {  
   @RequestMapping(value="/upload", method=RequestMethod.GET)  
   public @ResponseBody Result provideUploadInfo() {  
        Result res = new Result();  
        res.setError("You can upload a file by posting to this same URL.");  
     return res;  
   }  
   @RequestMapping(value="/upload", method=RequestMethod.POST, consumes = {"multipart/form-data"})  
   public @ResponseBody Result handleFileUpload( @RequestParam("result") String res, @RequestParam("cv") MultipartFile file, @RequestParam("pic") MultipartFile pic){  
        Result result = new Result();  
        ObjectMapper mapper = new ObjectMapper();  
           Result resNew = new Result();  
           try {  
                resNew = mapper.readValue(res,Result.class);  
           } catch (Exception e) {  
                result.setError("JSON mapping failed"+e.getMessage());  
                return result;  
           }   
        if (!pic.isEmpty()) {  
             try {  
         byte[] bytes = pic.getBytes();  
         BufferedOutputStream stream =  
             new BufferedOutputStream(new FileOutputStream(new File("pic.jpg")));  
         stream.write(bytes);  
         stream.close();  
         result.setSuccess("You successfully uploaded "+resNew.getSuccess());  
         return result;  
       } catch (Exception e) {  
            result.setError("You failed to upload " + e.getMessage());  
       }  
     } else {  
          result.setError("You failed to upload because the file was empty.");  
        }  
     if (!file.isEmpty()) {  
       try {  
         byte[] bytes = file.getBytes();  
         BufferedOutputStream stream =  
             new BufferedOutputStream(new FileOutputStream(new File("new.txt")));  
         stream.write(bytes);  
         stream.close();  
         result.setSuccess("You successfully uploaded "+resNew.getSuccess());  
         return result;  
       } catch (Exception e) {  
            result.setError("You failed to upload " + e.getMessage());  
         return result;  
       }  
     } else {  
          result.setError("You failed to upload because the file was empty.");  
       return result;  
     }  
   }  
 }  

Points to look on

  • @RequestParam with the property tag name should be used to get Stringified JSON.
  • @RequestPart with the property tag name should be used to get MultiPartFile.
  • Object Mapper will help to make String to Object.
Thanks,