MultipartFile issue, unable to convert to File - java

I am trying to to upload above 1 GB file, I am using Spring Boot.
I've tried with below code, but I am getting as Out of Memory error.
public void uploadFile(MultipartFile file) throws IOException {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
String uploadFile= restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(new FileSystemResource(convert(file)), headers), String.class).getBody();
} catch (Exception e) {
throw new RuntimeException("Exception Occured", e);
}
}
private static File convert(MultipartFile file) throws IOException {
File convFile = new File(file.getOriginalFilename());
convFile.createNewFile();
FileOutputStream fos = new FileOutputStream(convFile);
fos.write(file.getBytes());
fos.close();
return convFile;
}
The Main problem I am facing is, I am unable to convert MultipartFile to java.io.File.
I've even tried replacing FileSystemResource with ByteArrayResource, but still getting OOM error.
I've even tried using below code too:
private static File convert(MultipartFile file) throws IOException {
CommonsMultipartFile commonsMultipartFile = (CommonsMultipartFile) multipartFile;
FileItem fileItem = commonsMultipartFile.getFileItem();
DiskFileItem diskFileItem = (DiskFileItem) fileItem;
String absPath = diskFileItem.getStoreLocation().getAbsolutePath();
File file = new File(absPath);
}
But I am getting below exception for above snippet:
org.springframework.web.multipart.commons.CommonsMultipartFile cannot
be cast to org.springframework.web.multipart.MultipartFile
Could anyone please tell me on how to convert MultipartFile to java.io.File?
And also is there any other approach better than FileSystemResource bcoz I will have to create new file in server everytime before uploading. If file is more than 1GB, another 1 GB new file has to be created on server side, and has to manually delete that file again, which I personally didn't like this approach.

getBytes() tries to load the whole byte array into memory which is causing your OOM what you need to do is stream the file and write it out.
Try the following:
private static Path convert(MultipartFile file) throws IOException {
Path newFile = Paths.get(file.getOriginalFilename());
try(InputStream is = file.getInputStream();
OutputStream os = Files.newOutputStream(newFile))) {
byte[] buffer = new byte[4096];
int read = 0;
while((read = is.read(buffer)) > 0) {
os.write(buffer,0,read);
}
}
return newFile;
}
I changed your method to return a Path instead of File which is part of the java.nio package. The package is preferred over java.io as its been optimized more.
If you do need a File object you can call newFile.toFile()
Since it returns a Path object you can use the java.nio.file.Files class to relocate the file to your preferred directory once it has been written out
private static void relocateFile(Path original, Path newLoc) throws IOException {
if(Files.isDirectory(newLoc)) {
newLoc = newLoc.resolve(original.getFileName());
}
Files.move(original, newLoc);
}

Related

Generated zip file is always corrupted after download on Mac

Lets say that I have a collection of objects which I would like to wrap with zip. My zip conversion class looks like:
public class ZipFile {
public byte[] createZipByteArray(String fileName, byte[] content) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (byteArrayOutputStream; ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
zipOutputStream.setLevel(Deflater.NO_COMPRESSION);
ZipEntry zipEntry = new ZipEntry(fileName);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(content);
zipOutputStream.closeEntry();
zipOutputStream.flush();
}
return byteArrayOutputStream.toByteArray();
}
}
My controller looks like:
#GetMapping(value = "objectWithDetails/zip", produces = "application/zip")
#RolesAllowed("QUERY_DATA")
public ResponseEntity<byte[]> getObjectWithDetails(String limit, HttpServletResponse response) throws IOException {
response.setContentType("application/zip");
List<ObjectSpecification> objectRepresentation = recipeService.getObjectRepresentation(limit);
byte[] objectsBytes = objectMapper.writeValueAsBytes(objectRepresentation);
ZipFile zipFile = new ZipFile();
return ResponseEntity
.ok()
.contentType(MediaType.valueOf("application/zip"))
.header("Content-Encoding", "UTF-8")
.header("Content-Disposition", String.format("attachment; filename=\"details_%s.zip\"", dateTimeProvider.nowDate()))
.body(zipFile.createZipByteArray(String.format("details_%s.zip", dateTimeProvider.nowDate()), objectsBytes));
}
In swagger, I have a download option for the generated zip files. After all, when I try to open the downloaded file I receive the following:
Unable to expand filename.zip. It is in an unsupported format.
I tried closing ZipOutputStream and ByteArrayOutputStream in a different configuration. Applied a different option for the content type in the controller but still, I cannot find out why all zip files are corrupted. I will be grateful for suggestions on what I miss. Cheers!
I managed to find out the root cause with #k314159
, namely, I had a zip file wrapped with a zip file. New version which works as expected.
public static byte[] createZipByteArray(String fileName, byte[] content) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (byteArrayOutputStream; ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
zipOutputStream.setLevel(Deflater.DEFLATED);
ZipEntry zipEntry = new ZipEntry(fileName);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(content);
zipOutputStream.closeEntry();
zipOutputStream.flush();
}
return byteArrayOutputStream.toByteArray();
}
and controller
#GetMapping(value = "objectWithDetails", produces = "application/zip")
#RolesAllowed("QUERY_DATA")
public ResponseEntity<byte[]> getObjectWithDetails(String limit, HttpServletResponse response) throws IOException {
response.setContentType("application/zip");
List<ObjectSpecification > objectRepresentation = objectService.getObjectRepresentation(limit);
byte[] objectsBytes = objectMapper.writeValueAsBytes(objectRepresentation);
return ResponseEntity
.ok()
.contentType(MediaType.valueOf("application/zip"))
.header("Content-Encoding", "UTF-8")
.header("Content-Disposition", getObjectFileName("attachment; filename=objects_"))
.body(createZipByteArray(getObjectFileName("objects_"), objectsBytes));
}

How get instance of HttpServletResponse for download files with JAVA EE

I have a file txt on the server (previously generated). When user clicks on button it generates the file, now I want (additionally) download the file inside my function. But I can't make it work(I'm new on JAVA EE), cause I don't know how to get HttpServletResponse.
From web I call function with this:
#Path("getreport")
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public JSONObject getreport(CommonInput input) {
JSONObject j = objectmapper.conertValue(reportBean.getreport(),JSONObject.class);
return j;
}
reprotBean has function:
public void getreport() {
//...doing many things
//generating my file
List<String> lines = new ArrayList<>();
lines.add("star file");
//..adding many lines
Path file = Paths.get("C:\\Users\\myuser\\file.txt");
Files.write(file, lines, StandardCharsets.UTF_8);
downloadFile();
//...doing many things
}
I found this way to download my file:
public void downloadFile(HttpServletResponse response){
String sourceFile = ""C:\\Users\\myuser\\file.txt"";
try {
FileInputStream inputStream = new FileInputStream(sourceFile);
String disposition = "attachment; fileName=outputfile.txt";
response.setContentType("text/txt");
response.setHeader("Content-Disposition", disposition);
response.setHeader("content-Length", String.valueOf(stream(inputStream, response.getOutputStream())));
} catch (IOException e) {
logger.error("Error occurred while downloading file {}",e);
}
}
private long stream(InputStream input, OutputStream output) throws IOException {
try (ReadableByteChannel inputChannel = Channels.newChannel(input); WritableByteChannel outputChannel = Channels.newChannel(output)) {
ByteBuffer buffer = ByteBuffer.allocate(10240);
long size = 0;
while (inputChannel.read(buffer) != -1) {
buffer.flip();
size += outputChannel.write(buffer);
buffer.clear();
}
return size;
}
}
When I try to use downloadFile(), it requires HttpServletResponse, and I don't have that parameter. I can't understand how to get that (how it works), or do I have to use another method for download my file?
All solutions I found requires HttpServletResponse (download files from browsers)
If you have that file generated already. Just need write it to HttpServletResponse
resp.setContentType("text/plain");
resp.setHeader("Content-disposition", "attachment; filename=sample.txt");
try(InputStream in = req.getServletContext().getResourceAsStream("sample.txt");
OutputStream out = resp.getOutputStream()) {
byte[] buffer = new byte[ARBITARY_SIZE];
int numBytesRead;
while ((numBytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, numBytesRead);
}
}
Be sure to make your file to be accessed by ServeletContext
If you are using Spring Rest framework. Can refer to below
#GetMapping("/download")
public ResponseEntity<byte[]> downloadErrorData() throws Exception {
List<Employee> employees = employeeService.getEmployees();
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(employees);
byte[] isr = json.getBytes();
String fileName = "employees.json";
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentLength(isr.length);
respHeaders.setContentType(new MediaType("text", "json"));
respHeaders.setCacheControl("must-revalidate, post-check=0, pre-check=0");
respHeaders.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);
return new ResponseEntity<byte[]>(isr, respHeaders, HttpStatus.OK);
}
credit to: https://www.jeejava.com/file-download-example-using-spring-rest-controller/

corrupt pdf file while writing the bytes to stream

Facing a problem while implementing a java-ws service for downloading a pdf file from another webservice. Below is the piece of code for the same.decode() is used because of the webservice(this java code is invoking) is responding with encoded binary-base-64. I could see the PDF is downloaded in the given location but when i open with pdf reader, it says the file is corrupt. Could you please help me ?
public DownloadFileResponse DownloadResponseMapper(Header header, DownloadDocumentResponseType response){
DownloadFileResponse result = new DownloadFileResponse();
result.setHeader(header);
Status status = new Status();
status.setStatusCode(String.valueOf(String.valueOf(response.getStatus().getStatusCode())));
status.setStatusMessage(response.getStatus().getMessage());
result.setStatus(status);
if(String.valueOf(String.valueOf(String.valueOf(response.getStatus().getStatusCode()))) != "0") {
String qNameFile = FileExchange.getProperty("fileSystem.sharedLocation") + "/" + "result.pdf";
try {
byte[] fileContent = FileUtil.decode(response.getFile());
System.out.println(response.getFile());
FileUtil.writeByteArraysToFile(qNameFile, fileContent);
} catch (Exception e) {
_logger.info(e.getStackTrace());
}
// calculate the hash of the file using two algorithm SHA-256/SHA-512
List<FileHashType> hashes = FileUtil.calculateHash(result.getFile());
result.setFileHash(hashes);
}
return result;
}
public static void writeByteArraysToFile(String fileName, byte[] content) throws IOException {
File file = new File(fileName);
BufferedOutputStream writer = new BufferedOutputStream(new FileOutputStream(file));
writer.write(content);
writer.flush();
writer.close();
}

Spring REST - create ZIP file and send it to the client

I want to create a ZIP file that contains my archived files that I received from the backend, and then send this file to a user. For 2 days I have been looking for the answer and can't find proper solution, maybe you can help me :)
For now, the code is like this (I know I shouldn't do it all in the Spring controller, but don't care about that, it is just for testing purposes, to find the way to make it works):
#RequestMapping(value = "/zip")
public byte[] zipFiles(HttpServletResponse response) throws IOException {
// Setting HTTP headers
response.setContentType("application/zip");
response.setStatus(HttpServletResponse.SC_OK);
response.addHeader("Content-Disposition", "attachment; filename=\"test.zip\"");
// Creating byteArray stream, make it bufferable and passing this buffer to ZipOutputStream
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(byteArrayOutputStream);
ZipOutputStream zipOutputStream = new ZipOutputStream(bufferedOutputStream);
// Simple file list, just for tests
ArrayList<File> files = new ArrayList<>(2);
files.add(new File("README.md"));
// Packing files
for (File file : files) {
// New zip entry and copying InputStream with file to ZipOutputStream, after all closing streams
zipOutputStream.putNextEntry(new ZipEntry(file.getName()));
FileInputStream fileInputStream = new FileInputStream(file);
IOUtils.copy(fileInputStream, zipOutputStream);
fileInputStream.close();
zipOutputStream.closeEntry();
}
if (zipOutputStream != null) {
zipOutputStream.finish();
zipOutputStream.flush();
IOUtils.closeQuietly(zipOutputStream);
}
IOUtils.closeQuietly(bufferedOutputStream);
IOUtils.closeQuietly(byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
But the problem is, that using the code, when I enter the URL localhost:8080/zip, I get a file test.zip.html instead of .zip file.
When I remove .html extension and leave just test.zip it opens correctly. So my questions are:
How to avoid returning this .html extension?
Why is it added?
I have no idea what else can I do. I was also trying replace ByteArrayOuputStream with something like:
OutputStream outputStream = response.getOutputStream();
and set the method to be void so it returns nothing, but It created .zip file which was damaged?
On my MacBook after unpacking the test.zip I was getting test.zip.cpgz which was again giving me test.zip file and so on.
On Windows the .zip file was damaged as I said and couldn't even open it.
I also suppose, that removing .html extension automatically will be the best option, but how?
Hope it is no as hard as It seems to be :)
Thanks
The problem is solved.
I replaced:
response.setContentType("application/zip");
with:
#RequestMapping(value = "/zip", produces="application/zip")
And now I get a clear, beautiful .zip file.
If any of you have either better or faster proposition, or just want to give some advice, then go ahead, I am curious.
#RequestMapping(value="/zip", produces="application/zip")
public void zipFiles(HttpServletResponse response) throws IOException {
//setting headers
response.setStatus(HttpServletResponse.SC_OK);
response.addHeader("Content-Disposition", "attachment; filename=\"test.zip\"");
ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream());
// create a list to add files to be zipped
ArrayList<File> files = new ArrayList<>(2);
files.add(new File("README.md"));
// package files
for (File file : files) {
//new zip entry and copying inputstream with file to zipOutputStream, after all closing streams
zipOutputStream.putNextEntry(new ZipEntry(file.getName()));
FileInputStream fileInputStream = new FileInputStream(file);
IOUtils.copy(fileInputStream, zipOutputStream);
fileInputStream.close();
zipOutputStream.closeEntry();
}
zipOutputStream.close();
}
#RequestMapping(value="/zip", produces="application/zip")
public ResponseEntity<StreamingResponseBody> zipFiles() {
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment; filename=\"test.zip\"")
.body(out -> {
var zipOutputStream = new ZipOutputStream(out);
// create a list to add files to be zipped
ArrayList<File> files = new ArrayList<>(2);
files.add(new File("README.md"));
// package files
for (File file : files) {
//new zip entry and copying inputstream with file to zipOutputStream, after all closing streams
zipOutputStream.putNextEntry(new ZipEntry(file.getName()));
FileInputStream fileInputStream = new FileInputStream(file);
IOUtils.copy(fileInputStream, zipOutputStream);
fileInputStream.close();
zipOutputStream.closeEntry();
}
zipOutputStream.close();
});
}
I am using REST Web Service of Spring Boot and I have designed the endpoints to always return ResponseEntity whether it is JSON or PDF or ZIP and I came up with the following solution which is partially inspired by denov's answer in this question as well as another question where I learned how to convert ZipOutputStream into byte[] in order to feed it to ResponseEntity as output of the endpoint.
Anyway, I created a simple utility class with two methods for pdf and zip file download
#Component
public class FileUtil {
public BinaryOutputWrapper prepDownloadAsPDF(String filename) throws IOException {
Path fileLocation = Paths.get(filename);
byte[] data = Files.readAllBytes(fileLocation);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
String outputFilename = "output.pdf";
headers.setContentDispositionFormData(outputFilename, outputFilename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
return new BinaryOutputWrapper(data, headers);
}
public BinaryOutputWrapper prepDownloadAsZIP(List<String> filenames) throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/zip"));
String outputFilename = "output.zip";
headers.setContentDispositionFormData(outputFilename, outputFilename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteOutputStream);
for(String filename: filenames) {
File file = new File(filename);
zipOutputStream.putNextEntry(new ZipEntry(filename));
FileInputStream fileInputStream = new FileInputStream(file);
IOUtils.copy(fileInputStream, zipOutputStream);
fileInputStream.close();
zipOutputStream.closeEntry();
}
zipOutputStream.close();
return new BinaryOutputWrapper(byteOutputStream.toByteArray(), headers);
}
}
And now the endpoint can easily return ResponseEntity<?> as shown below using the byte[] data and custom headers that is specifically tailored for pdf or zip.
#GetMapping("/somepath/pdf")
public ResponseEntity<?> generatePDF() {
BinaryOutputWrapper output = new BinaryOutputWrapper();
try {
String inputFile = "sample.pdf";
output = fileUtil.prepDownloadAsPDF(inputFile);
//or invoke prepDownloadAsZIP(...) with a list of filenames
} catch (IOException e) {
e.printStackTrace();
//Do something when exception is thrown
}
return new ResponseEntity<>(output.getData(), output.getHeaders(), HttpStatus.OK);
}
The BinaryOutputWrapper is a simple immutable POJO class I created with private byte[] data; and org.springframework.http.HttpHeaders headers; as fields in order to return both data and headers from utility method.

Sending a Zip file to Rest WebService

I'm trying to implement a method in my java based application that involves uploading a zip file to my server. Client is java, server is java (REST , jboss 7) . In the past I successfully managed to upload image files, but now, with a zip file i am having issues and my main doubt is if these issues are client related or server related (or both) .
So , my client looks like this
final HttpHeaders headers = HttpClientUtils.headersJSONAndAcceptJSON();
MultiValueMap<String, Object> requestMap = new LinkedMultiValueMap<String, Object>();
addMap("filename", filename, requestMap);
addMap("contenttype", contentType, requestMap);
addMap("type", type, requestMap);
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int b = -1;
//file data is the inputstream created from the File
while ( (b = filedata.read())>= 0 ) {
bout.write(b);
}
ByteArrayResource rs = new ByteArrayResource( bout.toByteArray() ){
#Override
public String getFilename() {
return "";
}
};
addMap("resource", rs, requestMap);
} catch (IOException e1) {
throw new IllegalStateException("Error");
}
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.setAccept(Arrays.asList(HttpClientUtils.mtypeJSONUtf8()));
final String url = this.baseURL + summaryURL;
try {
ResponseEntity<Summary> rEntity = restTemplate.exchange(
url,
HttpMethod.POST,
HttpClientUtils.entity(headers, requestMap),
Summary.class
(...)
and meanwhile on the server side I have
#POST
#Path("/")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("application/json; charset=UTF-8")
public Summary addImportedSummary(#MultipartForm FileUploadFormObj imp)
{
Summary importedSummary = new Summary();
Map<String , String> newpath = new HashMap<String, String>();
if(imp.getFileData() != null)
{
ZipInputStream zip = new ZipInputStream(imp.getFileData());
ZipEntry entry;
try {
while ((entry = zip.getNextEntry()) != null)
{
if(entry.getName().endsWith(".html") || entry.getName().endsWith(".htm"))
{
if(entry.getSize() > 0)
{
StringWriter writer = new StringWriter();
IOUtils.copy(zip, writer, "UTF-8");
String content = writer.toString();
//do something with the content
}
}
}
zip.close();
} catch (IOException e) {
throw new BadRequestException("Error " + e);
}
}
The problem happens when I try to copy the file content with IOUtils or any other reader. I always get the exception
ZipException too many length or distance symbols
Now, I think the problem might be in the way I am sending the data due to the file being a zip but I don't know exactly where the problem is. Did everyone ever ran into a similar problem?

Categories

Resources