Multipart file upload with spring RestTemplate and Jackson - java

I want to upload a file by calling a rest web-service.
This web-service need a MultipartFile.
I read here that I can do this : Multipart File Upload Using Spring Rest Template + Spring Web MVC
So, here is my code :
public Document uploadDocument(MultipartFile file) {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(backendURL + "documents/upload");
URI uri = builder.build().encode().toUri();
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("file", file);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity =
new HttpEntity<LinkedMultiValueMap<String, Object>>(map, headers);
try {
ResponseEntity<Document> responseEntity = restTemplate.exchange(uri, HttpMethod.POST, requestEntity, Document.class);
} catch (Exception e) {
e.getMessage(); // Crash here
}
return document.getBody();
}
Jackson try to serialize the file in JSON, but it fail with this error :
Could not write content: 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: org.springframework.web.multipart.support.StandardMultipartFile["inputStream"]->java.io.FileInputStream["fd"])
What can I do to disable the json serialization of the file ?

Thanks to Jekin Kalariya I found a working solution.
I create a temporary file from my MultipartFile and use it to create a FileSystemResource. I send this FileSystemResource instead of the MultipartFile.
Here is the working code :
public DocumentDetailed uploadDocumentInIfs(MultipartFile file, String userProfile) {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(backendURL + "documents/upload");
builder.queryParam("user", userProfile);
URI uri = builder.build().encode().toUri();
File tempFile = null;
try {
String extension = "." + getFileExtention(file.getOriginalFilename());
tempFile = File.createTempFile("temp", extension);
file.transferTo(tempFile);
} catch (IOException e) {
e.printStackTrace();
}
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("file", new FileSystemResource(tempFile));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
Document document = null;
try {
ResponseEntity<Document> responseEntity =
restTemplate.exchange(uri, HttpMethod.POST, requestEntity, Document.class);
document = responseEntity.getBody();
} catch (Exception e) {
e.getMessage();
}
return document;
}

Related

Spring not generating Content Type

I'm using SpringBoot 3.0.1 and I'm trying to get a file stored in the backend using Axios.
The controller is the following:
#GetMapping(value = "/api/files/{fileName}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<?> getFile(final #PathVariable("fileName") String fileName) {
try {
Path filePath = Path.of(fileName);
File file = filePath.toFile();
HttpHeaders responseHeaders = new HttpHeaders();
String filename = filePath.getFileName().toString();
responseHeaders
.setContentDisposition(ContentDisposition.builder("attachment")
.filename(filename, StandardCharsets.UTF_8)
.build());
FileSystemResource fileSystemResource = new FileSystemResource(file);
return ResponseEntity
.ok()
.headers(responseHeaders)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(file.length())
.lastModified(file.lastModified())
.body(fileSystemResource);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
When I get the answer (status is 200), the header I've set in the controller is not given. In particular, the Content-Disposition header is not defined in the answer.
I'm wondering if there is any missing configuration that must be set in Sprint Boot in order to be allowed to set a custom header. Anyone who knows what can cause this and how to fix it?

How to upload file with utf-8 symbols in name on jira task as attachment using spring boot

I am trying to upload file on jira task as attachment.
for example I am trying to upload file with the name "test-äöü.txt" and it appears on jira like "test-???.txt"
Here is my original code:
RestTemplate restTemplate = restTemplateFactory.getInstance(RestTemplateUtil.getRequestFactory(proxyInfo,useEncryption));
File file = new File(filepath,filename);
MultiValueMap<String, Object> multipartMap = new LinkedMultiValueMap<>();
FileSystemResource var = new FileSystemResource(file);
multipartMap.add("file", var);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(multipartMap,
RestTemplateUtil.buildHttpHeadersForUploadAttachment(jiraUser, jiraPass,useEncryption)
ResponseEntity<String> result = restTemplate.exchange( jiraAdressAttachFile, HttpMethod.POST, request, String.class);
I am setting "Authorization" , "X-Atlassian-Token" headers in buildHttpHeadersForUploadAttachment metgod and "Content-Type" with MediaType.MULTIPART_FORM_DATA value
Like I said file goes on jira with name "test-???.txt", than I searched some solutions and tried them but nothing worked for me.
Here is the solutions I tried to solve my problem:
restTemplate.getMessageConverters().stream()
.filter(FormHttpMessageConverter.class::isInstance)
.map(FormHttpMessageConverter.class::cast)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Failed to find FormHttpMessageConverter"))
.setMultipartCharset(StandardCharsets.UTF_8);
Did not work
restTemplate.getMessageConverters().stream()
.filter(AllEncompassingFormHttpMessageConverter.class::isInstance)
.map(AllEncompassingFormHttpMessageConverter.class::cast)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Failed to find AllEncompassingFormHttpMessageConverter"))
.setMultipartCharset(StandardCharsets.UTF_8);
Did not work
restTemplate.getMessageConverters().add(0, new FormHttpMessageConverter() {
#Override
protected String getFilename(Object part) {
if (part instanceof Resource) {
Resource resource = (Resource) part;
try {
return new String(resource.getFilename().getBytes(StandardCharsets.UTF_8));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}
});
In this example I tried every possible value of StandardCharsets but again nothing worked
I tried this one too
FormHttpMessageConverter converter = new FormHttpMessageConverter();
converter.setMultipartCharset(Charset.forName("UTF-8"));
restTemplate.getMessageConverters().add(0, converter);
and this
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
StringHttpMessageConverter stringMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
ByteArrayHttpMessageConverter byteArrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
byteArrayHttpMessageConverter.setDefaultCharset(Charset.forName("UTF-8"));
ResourceHttpMessageConverter resourceHttpMessageConverter = new ResourceHttpMessageConverter();
resourceHttpMessageConverter.setDefaultCharset(Charset.forName("UTF-8"));
FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
formHttpMessageConverter.setMultipartCharset(Charset.forName("UTF-8"));
AllEncompassingFormHttpMessageConverter allEncompassingConverter = new AllEncompassingFormHttpMessageConverter();
allEncompassingConverter.setCharset(Charset.forName("UTF-8"));
allEncompassingConverter.setMultipartCharset(Charset.forName("UTF-8"));
allEncompassingConverter.setPartConverters(Arrays.asList(stringMessageConverter,byteArrayHttpMessageConverter,resourceHttpMessageConverter));
for (Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator(); iterator.hasNext(); ) {
HttpMessageConverter conv = iterator.next();
if (conv instanceof AllEncompassingFormHttpMessageConverter) {
iterator.remove();
}
}
messageConverters.add(allEncompassingConverter);
restTemplate.setMessageConverters(messageConverters);
again bad result.
Am I doing something wrong in my original code or in a possible solutions I tried and described?
or is there something I did not tried and I must do to work my code properly ?
Before posting request, in File, FileSystemResource and MultiValueMap<String, Object> objects this file has correct name like "test-äöü.txt"
update:
issue is with spring boot version less than 2.0

SpringBoot : how to download file from a MicroService that returns a ResponseEntity<Resource>?

I'm trying to download a File (PDF or Excel) returned by a Spring Boot Micro Service.
So, I have a GWT application (let's call it "A") and a Micro Service.
The problem is : the micro service creates the required file (perfect) but my browser doesn't display the "Save as" popup. Nothing happens.
Here is what I have so far :
#GetMapping(value = "/report")
public ResponseEntity<Resource> generateFullReport(HttpServletResponse response, HttpServletRequest request,
#RequestParam("name") String name, #RequestParam("output") String output) throws BirtException {
log.info("Generating full report: " + name + "; format: " + output);
EnumOutputType format = EnumOutputType.from(output);
String filePath = birtReportManagerService.generateMainReport(name, format, response, request);
File file = new File(filePath);
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="+file.getName());
headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
headers.add(HttpHeaders.PRAGMA, "no-cache");
headers.add(HttpHeaders.EXPIRES, "0");
ByteArrayResource resource = null;
try {
resource = new ByteArrayResource(Files.readAllBytes(Paths.get(filePath)));
} catch (IOException e) {
e.printStackTrace();
}
ResponseEntity responseEntity = ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
.body(resource);
// file.delete();
return responseEntity;
}
And :
#Override
public void generateBirtReport(String reportName, String reportFormat, HashMap<String, String> parameters) {
logger.debug("Entering REST generateBirtReport {}", reportName+"."+reportFormat);
LinkedMultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
for(Map.Entry<String, String> entry : parameters.entrySet()){
multiValueMap.add(entry.getKey(), entry.getValue());
}
URI uri = UriComponentsBuilder.fromUriString(URL_GLOBAL_REST + "/v1/reports/report")
.queryParam("name", reportName)
.queryParam("output", reportFormat)
.queryParams(multiValueMap)
.build()
.encode()
.toUri();
HttpHeaders httpHeaders = new HttpHeaders();
HttpEntity<String> request = new HttpEntity<>(httpHeaders);
ResponseEntity<Resource> response = restTemplate.exchange(uri, HttpMethod.GET, request, Resource.class);
}
I would like to see that dialog box :
Version : SpringBoot : 2.1.5 / Java : 8

java.lang.IllegalArgumentException map has no value for "orderId" in Spring Restful service

I'm working on a Payment related Restful service. when I try to call the API it occurs an exception called java.lang.IllegalArgumentException map has no value for orderId
As a background, I created HttpHeaders and LinkedMultiValueMap and put values to it and called the API.
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
LinkedMultiValueMap<String, String> uriVars = new LinkedMultiValueMap<>();
uriVars.add("merchantId", _paymentInstrument.getAcquirerMid());
uriVars.add("orderId", _paymentInstrument.getOrderId().toString());
uriVars.add("transactionId", _paymentInstrument.getTransactionId().toString());
VoidRequest voidRequest = createVoidRequest(_paymentInstrument.getTargetTransactionId());
HttpEntity<VoidRequest> requestEntity = new HttpEntity<>(voidRequest, headers);
TransactionResponse voidResponse = null;
try {
ResponseEntity<TransactionResponse> responseEntity = restTemplate.exchange(getEnvironment().getProperty(IPG_TRANSACTION_URL),
HttpMethod.PUT, requestEntity, TransactionResponse.class, uriVars);
voidResponse = responseEntity.getBody();
log.info(LogSupport.PG_LOGS_CARGILLS_VOID_TRX + "[AcquirerMID ={};TransactionId ={};ResponseCode={};ResponseText={}]", _paymentInstrument.getAcquirerMid(),
_paymentInstrument.getTransactionId(), voidResponse.getResponse().getAcquirerCode(), voidResponse.getResponse().getAcquirerMessage());
} catch (ResourceAccessException rae) {
throw rae;
} catch (Exception e) {
voidResponse = new TransactionResponse();
voidResponse.setResult("ERROR");
ErrorResponse error = new ErrorResponse();
error.setCause("EXCEPTION");
error.setExplanation(e.getMessage());
}
This occurs an exception java.lang.IllegalArgumentException map has no value for "orderId".
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url).queryParam("criterion", criterion);
Product product = restTemplate.getForObject(builder.build().toUri(), Product.class);
Check typo mistakes for orderId
If you declare dynamic values in property file, compare name with property Ex: {orderId}

How to send large file using Spring REST multipart in chunks (REST client)

I am using spring REST to write a client which will upload a file to DB.
Following is the server side controller code which I can not change :
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<UploadResponseDto> uploadFile(#RequestParam("file") MultipartFile file) throws IOException {
String contentType = file.getContentType();
if ( contentType == null || !contentType.equalsIgnoreCase(APPLICATION_OCTET_STREAM)) {
contentType = APPLICATION_OCTET_STREAM;
}
GridFSFile gridFSFile = gridFsTemplate.store(file.getInputStream(), file.getOriginalFilename(), contentType);
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
String fileLocation = linkTo(FileAttachmentController.class).slash(gridFSFile.getId()).toUri().toString();
headers.add(LOCATION, fileLocation);
UploadResponseDto uploadResponseDto = new UploadResponseDto(file.getOriginalFilename(), fileLocation);
return new ResponseEntity<>(uploadResponseDto, headers, HttpStatus.CREATED);
}
And my client side code for sending file is :
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setBufferRequestBody(false);
RestTemplate restTemplate = new RestTemplate(factory);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + token);
headers.set("Accept", "application/json");
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
File file = new File(fileToUpload);
MultiValueMap<String, Object> data = new LinkedMultiValueMap<String, Object>();
ByteArrayResource resource = new ByteArrayResource(
Files.readAllBytes(Paths.get(fileToUpload))) {
#Override
public String getFilename() {
return file.getName();
}
};
data.add("file", resource);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(
data, headers);
ResponseEntity<Map> apiResponse = null;
apiResponse = restTemplate.exchange(
"http://{end_point_url}",
HttpMethod.POST, requestEntity, Map.class);
But when I use this code to send lets say 50 MB file, it throws "413 Request entity too large error"
Can somebody please help me out on how to send a large file in chunks?
Thanks & Regards,
Vikas Gite
You can specify a size of the upload file by using
org.springframework.web.multipart.commons.CommonsMultipartResolver
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(54525952); //...specify your size of file (20971520 - 20 MB) (54525952 - 52 MB)
return multipartResolver;
}
Update
Okay so you have set multipartMaxFileSize, but along with this you also need to set max request size if you have a single file that's greater than 10MB
Seems you are using Spring 4.x
So config goes like
spring.http.multipart.maxFileSize
spring.http.multipart.maxRequestSize
Official Source
Depricated:
By default SimpleClientHttpRequestFactory buffers the request body internally.
Make it false
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setBufferRequestBody(false);
Source

Categories