Problem while uploading MultiValueMap using FeignClient for MultipartFile receiver API - spring-boot

I am facing issues while sending MultipartFile from one service to another service.
API which I want to call is,
#PostMapping(value = "/posts/{postId}/comments/{commentId}/attachments")
#JsonView(CaseJsonView.ClientWithComments.class)
public ResponseEntity<?> createCommentAttachment(#PathVariable final String postId, #PathVariable final String commentId, #RequestParam("payload") final MultipartFile multipartFile, #RequestParam final String filename, final HttpServletRequest request) throws JsonProcessingException {
try {
FileUtils.writeByteArrayToFile(new File("C:\\CODE\\data\\" + filename), multipartFile.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
Here empty file is getting created when I call this API like below,
FeignClient
#FeignClient(name = "post-service")
public interface CommentClient {
#RequestMapping(method = RequestMethod.POST, value = "/posts/{postId}/comments/{id}/attachments?filename={filename}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
void storeCommentAttachmentPayload(#PathVariable("postId") String postId, #PathVariable("id") String id, #RequestBody MultiValueMap<String, Object> map, #PathVariable("filename") String filename);
}
And I am using this FeignClient like below,
public void sendAttachment() {
//Adding only attachment code.
// Here attachmentClient is some other FeignClient which is returning attachment.
final Resource resource = attachmentClient.getAttachmentPayloadById(attachementId);
final MultipartFile multipartFile = new InMemoryMultipartFile(filename, filename,
mimeType, IOUtils.toByteArray(resource.getInputStream()));
final MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
final ByteArrayResource byteArrayResource = new
ByteArrayResource(multipartFile.getBytes()) {
#Override
public String getFilename() {
return filename == null ? "" : filename;
}
};
map.add(PAYLOAD, byteArrayResource);
commentService.storeCommentAttachmentPayload(postId commentId, map, filename);
}
Observation:
Here my observation is that when I save files on disk from this method, all data is shown properly.
But at the receiver end empty file is saved.
Another observation is that somehow byte array size is less at the receiver end than the sender end. Check the below image,
One more observation is that text files are uploaded properly.

So finally I found a solution to my problem. Instead of uploading a file using FeignClient, I am uploading using RestTemplate as below,
final List<ServiceInstance> postServiceInstances = discoveryClient.getInstances("post-service");
if (postServiceInstances != null && !postServiceInstances.isEmpty()) {
final HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<MultiValueMap<String, Object>>(multiValuMap);
final ResponseEntity<String> response = restTemplate
.postForEntity(postServiceInstances.get(0).getUri() + "/posts/" + postId + "/comments/" + commentId + "/attachments?filename=" + filename, entity, String.class);
if (response.getStatusCode() != HttpStatus.CREATED) {
throw new Exception("Exception from post-service: " + response.getStatusCode());
}
} else {
throw new Exception("No post-service instance found");
}
Not actually a perfect solution but it is solving my purpose.
Also, I have added RestTemplate Interceptor which adds a token in my request.
Still, the solution using FeignClient will be appreciated.

Related

406 error always happening with spring rest controller

I'd like to have your opinion on a error always throwed in my spring boot rest controller. I got the following first controller accepting reading requests :
#RequestMapping(value="/read/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity myFunction(#PathVariable("file") String file) {
String[] parts = file.split("\\.");
String extension = parts[1];
List<SousBloc> resWord;
List<SousBloc> resPdf;
List<CvAvecBlocs> resExcel;
RestTemplate rt = new RestTemplate();
rt.getMessageConverters().add(new StringHttpMessageConverter());
if(extension.equals("xlsx")){
resExcel = rt.getForObject("http://localhost:8080/readExcel/"+file, List.class, 200);
return new ResponseEntity<>(resExcel, HttpStatus.OK);
}
else if(extension.equals("pdf")){
resPdf = rt.getForObject("http://localhost:8080/readPdf/"+file, List.class, 200);
return new ResponseEntity<>(resPdf, HttpStatus.OK);
}
else if(extension.equals("docx")){
resWord = rt.getForObject("http://localhost:8080/readWord/"+file, List.class, 200);
return new ResponseEntity<>(resWord, HttpStatus.OK);
}
return null;
}
There is my Reading Word Controller :
#Controller
public class ReadWordController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadWord readWord;
#RequestMapping(value="/readWord/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingWord(#PathVariable("file") String file) throws IOException {
String path = UPLOADED_FOLDER+file;
List<SousBloc> sousBlocs = readWord.extract(path);
return new ResponseEntity<>(sousBlocs, HttpStatus.OK);
}
}
Well this controller works fine and does the job.
Now there is my Reading Pdf Controller :
#Controller
public class ReadPdfController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadPdf readPdf;
#RequestMapping(value="/readPdf/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingPdf(#PathVariable("file") String file) throws IOException {
String path = UPLOADED_FOLDER+file;
List<SousBloc> blocs = readPdf.extract(path);
return new ResponseEntity<>(blocs, HttpStatus.OK);
}
}
It is contructed on the same model of the Reading Word Controller but it does not work. In debug, the program works fine until the return new ResponseEntity<>(blocs, HttpStatus.OK); that throws a 406 error null...
Do you know why ?
EDIT: I tried something strange and it worked... I put the following code :
#Controller
public class ReadWordController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadWord readWord;
#Autowired
ReadPdf readPdf;
#RequestMapping(value="/readWord/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingWord(#PathVariable("file") String file) throws IOException {
/*String path = UPLOADED_FOLDER+file;
List<SousBloc> sousBlocs = readWord.extract(path);
return new ResponseEntity<>(sousBlocs, HttpStatus.OK);*/
String path = "C:\\cvsUploades\\file.pdf";
List<SousBloc> blocs = readPdf.extract(path);
return new ResponseEntity<>(blocs, HttpStatus.OK);
}
}

Spring-boot MultipartFile issue with ByteArrayResource

I'm trying to implement a rest api consuming excel file. I'm using spring-boot and code is available here.
Code works fine when using FileSystemResource for payload. But i'm not able to make the code work with ByteArrayResource in replacement of FileSystemResource:
RestApi.java:
#RestController
public class RestApi {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#PostMapping("/api/upload")
public ResponseEntity<?> uploadFile(#RequestParam("file") MultipartFile uploadfile) {
LOGGER.debug("Single file upload!");
try {
LOGGER.info("\n\n ****** File name: {}, type {}! ************", uploadfile.getOriginalFilename(), uploadfile.getContentType());
this.processExcelFile(uploadfile.getInputStream());
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK);
}
private List<String> processExcelFile(InputStream stream) throws Exception {
List<String> result = new ArrayList<String>();
//Create Workbook instance holding reference to .xlsx file
try(XSSFWorkbook workbook = new XSSFWorkbook(stream);) {
//Get first/desired sheet from the workbook
XSSFSheet sheet = workbook.getSheetAt(0);
//Iterate through each rows one by one
Iterator<Row> rowIterator = sheet.iterator();
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
String cellValue = row.getCell(0).getRichStringCellValue().toString();
result.add(cellValue);
LOGGER.info("\n\n ****** Cell value: {} ************", cellValue);
}
return result;
}
}
}
RestApiTest:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RestApiTest {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private ResourceLoader loader;
#Test
public void testUploadFile() throws Exception {
Resource resource = this.loader.getResource("classpath:test.xlsx");
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
// parts.add("file", new FileSystemResource(resource.getFile()));
parts.add("file", new ByteArrayResource(IOUtils.toByteArray(resource.getInputStream())));
String response = this.restTemplate.postForObject("/api/upload", parts, String.class);
Assertions.assertThat(response).containsIgnoringCase("success");
}
}
I'm getting following error when running test:
java.lang.AssertionError:
Expecting:
<"{"timestamp":1487852597527,"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present","path":"/api/upload"}">
to contain:
<"success">
(ignoring case)
Any idea?
when using loader.getResource(...) you must use resource itself as answered above. So you don't need ByteArrayResource. I got this problem, but I'm not using resource from classpath. So if someone really need to use ByteArrayResource, here is my workaround
public class FileNameAwareByteArrayResource extends ByteArrayResource {
private String fileName;
public FileNameAwareByteArrayResource(String fileName, byte[] byteArray, String description) {
super(byteArray, description);
this.fileName = fileName;
}
#Override
public String getFilename() {
return fileName;
}
}
and then use it
parts.add("file", new FileNameAwareByteArrayResource("filename", byteArray));

Spring RestTemplate Handling Multiple Responses

I'm consuming a restful service, What I observed is based on the request I see multiple responses for the same end point.
For Ex : I request a GET call with the some parameters for a PDF document.
if the response is good and the content type is application/pdf its giving a pdf document.
if the document is not available, the content type is application/xml and the response is giving the error code, error description.
Any input is much appreciated !
Thanks,
Sudheer.
You can use the ResponseEntity class of Spring, you can set the class to return the object what you want. You can change the content type, and everything you want.
here there is an example of file
#RequestMapping(value = URIConstansts.GET_FILE, produces = { "application/json" }, method = RequestMethod.GET)
public #ResponseBody ResponseEntity getFile(#RequestParam(value="fileName", required=false) String fileName,HttpServletRequest request) throws IOException{
ResponseEntity respEntity = null;
byte[] reportBytes = null;
File result=new File("/home/arpit/Documents/PCAP/dummyPath/"+fileName);
if(result.exists()){
InputStream inputStream = new FileInputStream("/home/arpit/Documents/PCAP/dummyPath/"+fileName);
String type=result.toURL().openConnection().guessContentTypeFromName(fileName);
byte[]out=org.apache.commons.io.IOUtils.toByteArray(inputStream);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("content-disposition", "attachment; filename=" + fileName);
responseHeaders.add("Content-Type",type);
respEntity = new ResponseEntity(out, responseHeaders,HttpStatus.OK);
}else{
respEntity = new ResponseEntity ("File Not Found", HttpStatus.OK);
}
return respEntity;
}
Here there is an example of Json
#ResponseBody ResponseEntity<? extends AbstractResponse> createUser(#RequestBody String requestBody) {
if(!valid(requestBody) {
ErrorResponse errResponse = new ErrorResponse();
//populate with error information
return new ResponseEntity<ErrorResponse> (errResponse, HTTPStatus.BAD_REQUEST);
}
createUser();
CreateUserSuccessResponse successResponse = new CreateUserSuccessResponse();
// populate with more info
return new ResponseEntity<CreateUserSuccessResponse> (successResponse, HTTPSatus.OK);
}

test method with MultipartFile as a parameter

I've got this method
#RequestMapping(value = "/upload", method = RequestMethod.POST, produces = "text/plain")
#ResponseBody
public String uploadFile(#RequestParam("file") MultipartFile file) {
LOGGER.debug("Attempt to upload file with template.");
try {
return FileProcessUtils.processFileUploading(file);
} catch (UtilityException e) {
LOGGER.error("Failed to process file.", e.getWrappedException());
return null;
}
}
I want to test it using junit and powermock.
#Test
public void testUploadFile() {
MultipartFile file = buildMultipartFile();
mockStatic(FileProcessUtils.class);
FileProcessUtils.processFileUploading(file);
expectLastCall().andReturn(FILE_CONTENT);
replay(FileProcessUtils.class);
String fileContent = templateSupportRest.uploadFile(file);
verify(FileProcessUtils.class);
assertEquals(FILE_CONTENT, fileContent);
}
But the problem is that I need somehow to pass as MultipartFile object with specific FILE_CONTENT.
To do that I need to build instance of MultipartFile.
private MultipartFile buildMultipartFile() {
DiskFileItem diskFileItem = new DiskFileItem("file", "text/plain", true, "file", 100, null);
MultipartFile file = new CommonsMultipartFile(diskFileItem);
return file;
}
However I've got problems with DiskFileItem object. When I create it like this I get NullPointerException in getSize() method of DiskFileItem object.
The constructor of DiskFileItem itself has this parameters
String fieldName,
String contentType,
boolean isFormField,
String fileName,
int sizeThresold,
File repository
So the question is - how to instantiate DiskFileItem or is there another way to handle the situation?

#RequestPart with mixed multipart request, Spring MVC 3.2

I'm developing a RESTful service based on Spring 3.2. I'm facing a problem with a controller handling mixed multipart HTTP request, with a Second part with XMLor JSON formatted data and a second part with a Image file .
I am using #RequestPart annotation for receiving the request
#RequestMapping(value = "/User/Image", method = RequestMethod.POST, consumes = {"multipart/mixed"},produces="applcation/json")
public
ResponseEntity<List<Map<String, String>>> createUser(
#RequestPart("file") MultipartFile file, #RequestPart(required=false) User user) {
System.out.println("file" + file);
System.out.println("user " + user);
System.out.println("received file with original filename: "
+ file.getOriginalFilename());
// List<MultipartFile> files = uploadForm.getFiles();
List<Map<String, String>> response = new ArrayList<Map<String, String>>();
Map<String, String> responseMap = new HashMap<String, String>();
List<String> fileNames = new ArrayList<String>();
if (null != file) {
// for (MultipartFile multipartFile : files) {
String fileName = file.getOriginalFilename();
fileNames.add(fileName);
try {
file.transferTo(new File("C:/" + file.getOriginalFilename()));
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
responseMap.put("displayText", file.getOriginalFilename());
responseMap.put("fileSize", "" + file.getSize());
response.add(responseMap);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Accept", "application/json");
return new ResponseEntity<List<Map<String, String>>>(response,
httpHeaders, HttpStatus.OK);
}
User.java will be like this-
#XmlRootElement(name = "User")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int userId;
private String name;
private String email;
private String company;
private String gender;
//getter setter of the data members
}
To my understanding, using the #RequestPart annotation I would expect the XML multipart section to be evaluated depending on its Content-Type and finally un-marshalled into my User class (I'm using Jaxb2, the marshaller/unmarhaller is properly configured in the application context and the procedure is working fine for all the other controller methods when I pass the XML data as body and use the #RequestBody annotation).
But what is actually happening is that, although the file is correctly found and parsed as MultipartFile, the "user" part is never seen and the request is always failing, not matching the controller method signature.
I reproduced the problem with several clients type and I am confident the format of the multipart request is ok.
Please help me to solve this issue, Maybe some workaround will be there to receive mixed/multipart request.
Thanks and Regards,
Raghvendra
I have managed to solve the problem
Endpoint example:
#PostMapping("/")
public Document create(#RequestPart Document document,
#RequestPart(required = false) MultipartFile file) {
log.debug("#create: document({}), file({})", delegation, file);
//custom logic
return document;
}
Exception:
"error_message": "Content type 'application/octet-stream' not supported"
Exception is thrown from the next method:
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(HttpInputMessage,MethodParameter,Type)
Solution:
We have to create custom converter #Component, which implements HttpMessageConverter or HttpMessageConverter and knows about MediaType.APPLICATION_OCTET_STREAM. For simple workaround it's enough to extend AbstractJackson2HttpMessageConverter
#Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
/**
* Converter for support http request with header Content-Type: multipart/form-data
*/
public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false;
}
#Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return false;
}
#Override
protected boolean canWrite(MediaType mediaType) {
return false;
}
}
Not sure if you had fixed your problem, but I also had a similar problem where my JSON object was not getting picked up by my controller when mixing #RequestPart and MultipartFile together.
The method signature for your call looks correct:
public ResponseEntity<List<Map<String, String>>> createUser(
#RequestPart("file") MultipartFile file, #RequestPart(required=false) User user) {
// ... CODE ...
}
However make sure your request looks something like this:
POST /createUser
Content-Type: multipart/mixed; boundary=B0EC8D07-EBF1-4EA7-966C-E492A9F2C36E
--B0EC8D07-EBF1-4EA7-966C-E492A9F2C36E
Content-Disposition: form-data; name="user";
Content-Type: application/xml; charset=UTF-8
<user><!-- your user xml --></user>
--B0EC8D07-EBF1-4EA7-966C-E492A9F2C36E
Content-Disposition: form-data; name="file"; filename="A551A700-46D4-470A-86E7-52AD2B445847.dat"
Content-Type: application/octet-stream
/// FILE DATA
--B0EC8D07-EBF1-4EA7-966C-E492A9F2C36E--
You can use #RequestPart from
org.springframework.web.bind.annotation.RequestPart;
It is used as Combining #RequestBody and file upload.
Using #RequestParam like this
#RequestParam("file") MultipartFile file
you can upload only file and multiple single data (key value )
like
#RequestMapping(value = "/uploadFile", method = RequestMethod.POST, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
public void saveFile(
#RequestParam("userid") String userid,
#RequestParam("file") MultipartFile file) {
}
you can post JSON Object data and and File both using #RequestPart like
#RequestMapping(value = "/patientp", method = RequestMethod.POST, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<?> insertPatientInfo(
#RequestPart PatientInfoDTO patientInfoDTO,
#RequestPart("file") MultipartFile file) {
}
You are not limited to using multipart file uploads directly as controller method parameters. Your form objects can contain Part or MultipartFile fields, and Spring knows automatically that it must obtain the values from file parts and converts the values appropriately.
Above method can respond to the previously demonstrated multipart request containing a single file. This works because Spring has a built-in HTTP message converter that recognizes file parts. In addition to the javax.servlet.http.Part type, you can also convert file uploads to org.springframework.web.multipart.MultipartFile. If the file field permits multiple file uploads, as demonstrated in the second multipart request, simply use an array or Collection of Parts or MultipartFiles.
#RequestMapping(value = "/patientp", method = RequestMethod.POST, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<?> insertPatientInfo(
#RequestPart PatientInfoDTO patientInfoDTO,
#RequestPart("files") List<MultipartFile> files) {
}
Happy To Help...
I have managed to solve problem:
#SuppressWarnings("rawtypes")
#RequestMapping(value = "/DataTransfer", method = RequestMethod.POST, produces = {
MediaType.APPLICATION_JSON_UTF8_VALUE }, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE} )
#ApiOperation(value = "Sbm Data Transfer Service", response = Iterable.class)
#ApiResponses(value = { #ApiResponse(code = 200, message = "Successfully find."),
#ApiResponse(code = 400, message = "There has been an error."),
#ApiResponse(code = 401, message = "You are not authorized to save the resource"),
#ApiResponse(code = 403, message = "Accessing the resource you were trying to reach is forbidden"),
#ApiResponse(code = 404, message = "The resource you were trying to reach is not found") })
ResponseEntity processDataTransfer(#RequestPart(name="file") MultipartFile file, #RequestPart(name="param") DataTransferInputDto param);
have you tried
ResponseEntity<List<Map<String, String>>> createUser(
#RequestPart("file") MultipartFile file, #RequestBody(required=false) User user) {
or
ResponseEntity<List<Map<String, String>>> createUser(
#RequestPart("file") MultipartFile file, #RequestParam(required=false) User user) {
If this does not work can you show us mapping.xml

Resources