Multipart file with document name not found - java

I am getting this error in test case for following api.
Required request part 'document' is not present
#PostMapping(value = "/testModel/{modelName}", consumes = {
MediaType.MULTIPART_FORM_DATA_VALUE })
public ResponseEntity<Object> testModel(#PathVariable("modelName") String modelName,
#RequestPart MultipartFile document) {
}
following is the test case for above api
#Test
#Transactional
void testModel() throws Exception {
service.save(domainModelDTo);
FileInputStream fi2 = new FileInputStream(new File("test.pdf"));
MockMultipartFile file = new MockMultipartFile("file", "test.pdf", DEFAULT_BINARY.toString(), fi2);
log.debug("file name {}", file.getOriginalFilename()); // test.pdf print in log
log.debug("file length {}", file.getBytes().length); // 44782 print in log
mockMvc.perform(multipart(ENTITY_API_URL, domainModelDTo.getName()).file(file)).andExpect(status().isOk());
}
I have implemented this from the following link as it is:
https://www.baeldung.com/sprint-boot-multipart-requests

In your MockMultipartFile the first parameter (name) must match the request parameter property (in this case document).
So if you change your code to:
MockMultipartFile file = new MockMultipartFile("document", "test.pdf", DEFAULT_BINARY.toString(), fi2);
The problem should be resolved.

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 do I return a tar.gz file via http in Spring

I am using apache compress in order to create and compress a number of files into a tar.gz file. I am then attempting to create an endpoint that another application can contact in order to receive the tar.gz file.
I have tried a number of different methods but I cannot seem to get the desired output. Currently I have am able to return the compressed file as a .gz file but I need it as a .tar.gz file.
My Controller method looks like:
public ResponseEntity<?> getBundle(){
BundleService bundleService = new BundleService();
List<File>fileList = new ArrayList<File>();
List<String> implementationIds = policyService.getImplementationIdListForBundle(EnvironmentType.DEV, PlatformEngineType.REGO);
List<PolicyImplementation> implementationList = policyService.getImplementationsForBundle(implementationIds);
fileList = bundleService.createImplementationFiles(implementationList);
bundleService.createTarBall(fileList);
File tarball = new File("./policyadmin/bundleDirectory/bundle.tar");
//File gzippedTarball = bundleService.gzipTarBall();
String gzippedLocation = tarball.getAbsolutePath() + ".gz";
//File gzippedFile = new File(gzippedLocation);
Resource tarResource = new FileSystemResource(tarball);
//bundleService.deleteTarball(tarball);
return new ResponseEntity<Object>(tarResource, HttpStatus.OK); //! If I return a File instead of a resource gives me No converter for [class java.io.File] with preset Content-Type 'null']
}
I also have the following code to handle the request:
//GET: Endpoint for OPA to retrieve on the fly gzipped tarball for Bundles
#ApiOperation(value = "Method to retrieve on the fly gzipped tarball for bundles", nickname = "getBundle", notes="", response = Resource.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Request for Bundle.tar.gz received", response = Resource.class),
#ApiResponse(code = 404, message = "Unable to find requested Bundle.tar.gz")
})
#GetMapping(value = "/policyadmin/bundle", produces= { "application/gzip" })
default ResponseEntity<?> getBundle(){
return new ResponseEntity<File>(HttpStatus.NOT_IMPLEMENTED);
}
Note: Make sure you've tar.gz file
Set the content type like:
public ResponseEntity<?> getBundle(HttpServletResponse response){
// Your other code
response.setContentType("application/gzip");
response.setHeader("Content-Disposition", "attachment; filename=\"bundle.tar.gz\"");
return new ResponseEntity<Object>(tarResource, HttpStatus.OK); //! If I return a File instead of a resource gives me No converter for [class java.io.File] with preset Content-Type 'null']
}

How to copy/rename a file and return it without altering the existing file?

I have a generic.exe file which doesn't contain any users detail in it.
I also have a REST API which takes the userId and returns a File to the client.
Now, what we want to implement in our project is that when someone hits the REST API, we want to take that generic.exe and rename it to manager_userId.exe and return back this "manager_userId.exe".
Points to be noted over here is that:
The generic.exe file should not be modified/deleted at all
When 2 users (userA and userB) hit that same API simultaneously , they should get their own copy of manager_userA.exe and manager_userB.exe
The code what I have written is
#RequestMapping(value = "/downloadExecutable", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON, produces = {MediaType.APPLICATION_OCTET_STREAM})
#ResponseBody
public Response downloadExecutable(#RequestBody DownloadExecutableRequest downloadExecutableRequest,
HttpServletRequest request, HttpServletResponse response) {
File file = downloadExecutable(downloadExecutableRequest, request, response,
getUserID(request), osDetails);
return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment;filename=" + file.getName()).build();
}
public File downloadExecutable(DownloadExecutableRequest downloadExecutableRequest, HttpServletRequest request,
HttpServletResponse response, String userId, String osDetails) {
File file = null;
String path = "/home/genericCopy/generic.exe";
synchronized (this) {
BufferedWriter fileWriter = null;
try {
File source = null;
source = new File(path);
Path destination = Paths.get("/tmp/");
Files.copy(source, destination.toFile());
fileWriter = new BufferedWriter(new FileWriter(destination.getFileName().toString()+"_"+userId));
file = new File(destination.getFileName().toString());
} catch (IOException e) {
} finally {
if (fileWriter != null) {
fileWriter.close();
}
}
}
return file;
}
The code is working , but it is creating a temporary file and then renaming it and then returning it back but it will keep on creating the copies of the file for each request.
Is there any smarter way that i can achieve not to create such temporary copies of the user specific files and also handle a scenario when 2 users hit the API simultaneously ?
The name of the file which is downloaded by user has no relationship to the name of the file on disk.
You can just specify any name of the file in the header and the user will see the name.
Specifically, you would just set the filename you want the user to see to the Content-Disposition header and always load the same exe file from the disk.
Something like this:
return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment;filename=executable_" + getUserID(request) + ".exe";
You don't need to do any copying in the downloadExecutable function.
You don't need to create a copy of generic.exe file to return it with changed name. You can use correctly parametrised Content-Disposition header, so it would return same file every time, with file name provided by user.
Here you can find example:
#RestController
public class DemoController {
#GetMapping(value = "/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
#ResponseBody
public ResponseEntity downloadExecutable(#RequestParam("userId") String userId) throws IOException {
byte[] file = Files.readAllBytes(Paths.get("/home/genericCopy/generic.exe"));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=generic_" + userId + ".exe")
.contentLength(file.length)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(file);
}
}
and result of executing this method:

Upload file using #FeignClient in SpringBoot

I have two apps, both are on SpringBoot. I'm trying to upload file from one app to another using #FeignClient
Here is Controller code that accepts file upload
#PostMapping(
path = "/result/output/{resultId}",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseStatus(HttpStatus.OK)
public Result uploadOutputProviderMetadata(#PathVariable(value = "resultId") UUID resultId,
#RequestParam("file") MultipartFile multipartFile) {
return resultService.storeResult(providerSearchTaskId, getString(multipartFile));
}
Here is the test snippet call for this controller
new MockMultipartFile(
"file",
file.getName(),
"application/zip",
FileUtils.readFileToByteArray(file));
var resultAsString = getMockMvc()
.perform(MockMvcRequestBuilders
.multipart("/private/api/v1/result/output/" + resultId.toString(), getPort())
.file(multipartFile)
.contentType(MediaType.MULTIPART_FORM_DATA)
)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
Test works, MvcMock can upload file using MockMultipartFile and MediaType.MULTIPART_FORM_DATA
Here is my poor FeignClient from another app that tries to upload file
#FeignClient(name = "Client", url = "${server.url}")
trait UploadClient {
#PostMapping(
path = Array("/private/api/v1/result/output/{resultId}"),
consumes = Array(MediaType.MULTIPART_FORM_DATA_VALUE))
def uploadResult(#PathVariable resultId: UUID,
#RequestPart(name = "file") file: MultiValueMap[String, Object]): Result
}
}
#Configuration
#EnableFeignClients(clients = Array(classOf[UploadClient]))
class FeignActivationConfiguration {
#Bean
def clientFeignEncoder(messageConverters: ObjectFactory[HttpMessageConverters]): Encoder = {
new SpringFormEncoder(new SpringEncoder(messageConverters))
}
}
I have e2e test and feign throws
status 400 reading AdsManagerClient#uploadResult(UUID,MultiValueMap)
feign.FeignException$BadRequest: status 400 reading UploadClient#uploadResult(UUID,MultiValueMap)
Why?
I've added error decoder, controller wasn't very informative
// Error response code: 400, responseText: ERROR, reason: null
val errorMessage = s"Error response code: ${response.status()}, responseText: $responseText, reason: ${response.reason()} while calling $methodKey"

Unable to download file: Could not find acceptable representation

This has tons of example on the internet but for some reason I just cannot download a csv file. I keep getting the error:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation`.
Here is the code below for my #RestController:
#GetMapping(value = "/tasks/{taskId}/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<InputStreamResource> download(#PathVariable String taskId, #RequestParam String format) throws IOException {
var path = "string path to file"
var file = new UrlResource(Path.of(path).normalize().toUri());
return ResponseEntity
.ok()
.contentLength(file.contentLength())
.contentType(
MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(file.getInputStream()));
}
I set the header Accept: application/octet-stream but still get the same error. What could be wrong any ideas?

Categories