4

I have a spring controller that accepts a class named FileUploadBean on POST. The controller method looks like that:

First Controller:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<byte[]> uploadFile(final FileUploadBean fileUploadBean) throws IOException {
   // Some code that works fine here
} 

One of the FileUploadBean properties is of type MultipartFile.

Now, I'm trying to add some sort of wrapper controller (that will run on another server) that also accepts FileUploadBean and just forwards the request to the first controller:

Second (wrapper) Controller:

@RequestMapping(value="/upload", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<byte[]> uploadImage(final FileUploadBean fileUploadBean) throws IOException {
  ResponseEntity<byte[]> response = restTemplate.postForEntity([first controller url here], fileUploadBean, byte[].class);
  return response;
}

When I'm sending the request to the first controller I get:

org.springframework.http.converter.HttpMessageNotWritableException:

Could not write JSON: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.outbrain.images.beans.FileUploadBean["file"]->org.springframework.web.multipart.commons.CommonsMultipartFile["fileItem"]->org.apache.commons.fileupload.disk.DiskFileItem["inputStream"]->java.io.FileInputStream["fd"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.outbrain.images.beans.FileUploadBean["file"]->org.springframework.web.multipart.commons.CommonsMultipartFile["fileItem"]->org.apache.commons.fileupload.disk.DiskFileItem["inputStream"]->java.io.FileInputStream["fd"]) at org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.writeInternal

How can I make this request work?

Avi
  • 21,182
  • 26
  • 82
  • 121
  • Jackson is blowing up because it cannot serialize the objects that make up the MultipartFile or one of it's sub-properties. You could re-architect your method to not use a REST service and just post the files like normal, sans wrapper `FileUploadBean`. – CodeChimp Mar 25 '14 at 11:21
  • @CodeChimp - Could you elaborate on your solution? I did not quite get it – Avi Mar 25 '14 at 11:36
  • Similar question: http://stackoverflow.com/questions/4118670/sending-multipart-file-as-post-parameters-with-resttemplate-requests – holmis83 Mar 25 '14 at 13:13
  • @holmis83 - Thanks! Already saw this one though and it didn't help me enough. Also there's no accepted answer there. – Avi Mar 25 '14 at 13:20
  • 1
    Change the method to accept a simple form post with MultipartFile instead of what appears to be a REST call sending JSON. The problem is you are trying to call the first controller via what looks like a RESTful call, which I assume is accepting JSON given the error message. – CodeChimp Mar 25 '14 at 14:57
  • @CodeChimp - You're right. I also saw it in another question. I will soon update a full answer – Avi Mar 25 '14 at 15:01

2 Answers2

10

Well, after some struggling this is how I solved it. That's what I did in the second controller:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody
ResponseEntity<byte[]> uploadImage(final FileUploadBean fileUploadBean) throws Exception {
  File file = null;
  try {
    final MultiValueMap<String, Object> requestParts = new LinkedMultiValueMap<>();

    final String tmpImageFileName = IMAGE_TMP_DIR + fileUploadBean.getFile().getOriginalFilename();
    file = new File(tmpImageFileName);
    fileUploadBean.getFile().transferTo(file);
    requestParts.add("file", new FileSystemResource(tmpImageFileName));

    HttpHeaders headers = new HttpHeaders();
    headers.set("Content-Type", "multipart/form-data"); // Sending it like the client-form sends it

    ResponseEntity<byte[]> response = restTemplate.exchange(ImageUrlUtils.getUploadUrl(), HttpMethod.POST, new HttpEntity<>(requestParts, headers),
      byte[].class);

    return new ResponseEntity<>(response.getBody(), response.getStatusCode());
  } catch (Exception ex) {
    return new ResponseEntity<>((ex.getMessage).getBytes("UTF-8"),
      HttpStatus.INTERNAL_SERVER_ERROR);
  } finally {
    if (file != null && file.exists()) {
      file.delete();
    }
  }
}
Avi
  • 21,182
  • 26
  • 82
  • 121
1

I debug previous answer, and found this solution without save file to file system

    @PostMapping(value = "/upload")
public ResponseEntity<Object> upload(MultipartHttpServletRequest request) throws Exception {
    final MultiValueMap<String, Object> requestParts = new LinkedMultiValueMap<>();

    request.getParameterMap().forEach((name, value) -> requestParts.addAll(name, asList(value)));
    request.getMultiFileMap().forEach((name, value) -> {
        List<Resource> resources = value.stream().map(MultipartFile::getResource).collect(toList());
        requestParts.addAll(name, resources);
    });

    HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(requestParts, request.getRequestHeaders());
    return restTemplate.exchange(ImageUrlUtils.getUploadUrl() + "?" + request.getQueryString(),
                                 request.getRequestMethod(), requestEntity, Object.class);

}