How to convert byte array to ZipOutputStream in spring MVC? - java

Trying to read a zip file stored in a database as a byte array.
.zip is getting downloaded using the following code but the size of files included in the zip is none. No data is there.
I already went through lots of answers but not sure what's wrong with the following code.
Please assist.
#RequestMapping(value = ApplicationConstants.ServiceURLS.TRANSLATIONS + "/{resourceId}/attachments", produces = "application/zip")
public void attachments(HttpServletResponse response, #PathVariable("resourceId") Long resourceId) throws IOException {
TtTranslationCollection tr = translationManagementDAO.getTranslationCollection(resourceId);
byte[] fileData = tr.getFile();
// setting headers
response.setStatus(HttpServletResponse.SC_OK);
response.addHeader("Content-Disposition", "attachment; filename=\"attachements.zip\"");
ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream());
ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream(fileData));
ZipEntry ent = null;
while ((ent = zipStream.getNextEntry()) != null) {
zipOutputStream.putNextEntry(ent);
}
zipStream.close();
zipOutputStream.close();
}

You have to copy the byte data (content) of the zip file to the output as well...
This should work (untested):
while ((ent = zipStream.getNextEntry()) != null) {
zipOutputStream.putNextEntry(ent);
// copy byte stream
org.apache.commons.io.IOUtils.copy(zis.getInputStream(ent), zipOutputStream);
}
BTW: why you do not just simply forward the original zip byte content?
try (InputStream is = new ByteArrayInputStream(fileData));) {
IOUtils.copy(is, response.getOutputStream());
}
or even better (thanks to comment by #M. Deinum)
IOUtils.copy(fileData, response.getOutputStream());

Related

How to get an InputStream representing one file in a ZipInputStream

I have the following situation:
I am getting a Zip File sent over the network in the Form of a Byte Array. I don't want to save that file locally, but instead target an individual file in that archive and import it into my solution as an InputStream.
Thus far, I have managed to get to the point where I can identify the correct ZipEntry. However, from here all the Zip-tutorials continue by reading the entry by its file name, which naturally does not work in my case since the zip file does not exist locally.
private void InputStream getReqifInputStreamFrom(byte[] reqifzFileBytes){
ByteArrayInputStream inputStream = new ByteArrayInputStream(reqifzFileBytes);
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
String fileName = zipEntry.getName();
if (fileName.endsWith(REQIF_FILE_SUFFIX)) {
return ???;
}
zipEntry = zipInputStream.getNextEntry();
}
}
So, what can I do at that point to return an InputStream that represents exactly the one file that the ZipEntry represents at that point? I have considered just returning the zipInputStream, but since I don't know exactly how that one works I'm afraid that doing so would also include all files that are still in the stream after that file to be returned as well.
You're virtually there. Try
private InputStream getReqifInputStreamFrom(byte[] reqifzFileBytes) throws IOException {
InputStream result = null;
ByteArrayInputStream inputStream = new ByteArrayInputStream(reqifzFileBytes);
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
String fileName = zipEntry.getName();
if (fileName.endsWith(REQIF_FILE_SUFFIX)) {
result = zipInputStream;
break;
}
zipEntry = zipInputStream.getNextEntry();
}
return result;
}

How to return a zip file stream using Java Springboot

I use Springboot, I want to generate zip file and then return to frontend.
#PostMapping(value="/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<ZipOutputStream> export() {
// customService.generateZipStream() is a service method that can
//generate zip file using ZipOutputStream and then return this stream
ZipOutputStream zipOut = customService.generateZipStream();
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.header("Content-Type","application/octet-stream")
.body(zipOut)
}
The zip file can be generated correctly(in local dir) but I got below error when return stream to frontend:
spring.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
Then i checked in google and changed return type to ResponseEntity<StreamResponseBody>, but how should I change ZipOutputStream to StreamResponseBody in method body(...), the solution in google is create zip output stream within body() method like that:
// pseudocode
.body(out -> {
ZipOutputStream zipOut = new ZipOutputStream(out));
zipOut.putEntry(...);
zipOut.write(...);
zipOut.closeEntry();
... balabala
}
My question is how to use StreamResponseBody in this scenario or any alternative solution to return a zip stream that might a little large.
If you are using Spring 3, you can use a lot of the swagger annotation interfaces to help cleanly build your ResponseEntity while using the StreamingResponseBody to properly prototype the format to be expected by spring.
The body code here is a shortened way of mapping the ZipOutputStream stream into StreamingResponseBody type that the controller expects to return (.body(out -> {...}) does this in my code below).
The [controller] code would look like this:
#GetMapping(value = "/myZip")
#Operation(
summary = "Retrieves a ZIP file from the system given a proper request ID.",
responses = {
#ApiResponse(
description = "Get ZIP file containing data for the ID.",
responseCode = "200",
content = #Content(schema = #Schema(implementation = StreamingResponseBody.class))),
#ApiResponse(
description = "Unauthenticated",
responseCode = "401",
content = #Content(schema = #Schema(implementation = ApiErrorResponse.class))),
#ApiResponse(
description = "Forbidden. Access Denied.",
responseCode = "403",
content = #Content(schema = #Schema(implementation = ApiErrorResponse.class)))
})
public ResponseEntity<StreamingResponseBody> myZipBuilder(#RequestParam String id, HttpServletResponse response)
throws IOException {
final String fileName = "MyRequest_" + id + "_" + new SimpleDateFormat("MMddyyyy").format(new Date());
return ResponseEntity.ok()
.header(CONTENT_DISPOSITION,"attachment;filename=\"" + fileName + ".zip\"")
.contentType(MediaType.valueOf("application/zip"))
.body(out -> myZipService.build(id, response.getOutputStream()));
}
The code for your service build method simply needs to take in the whatever parms you need for the data, plus your ServletOutputStream responseOutputStream parm to allow you to build your ZipOutputStream object seeded by that stream.
In my little example below, you can see I build some CSV data in the buildDataLists method (not shown), which is just a List of List<String[]>.. I then take each of the top level List items and push them into the ZipOutputStream object using my streamWriteCsvToZip. The point is, you build your ZIP stream seeded with the responseOutputStream you were given from the controller. Once you have built your zip fully, make sure to close it up (in my case zos.close()). Then return the zos object to the controller.
/**
* Get ZIP file containing datafiles for a given request id
*
* #param id of the request
* #param responseOutputStream for streaming the zip results
* #return ZipOutputStream a ZIP file stream for the contents
* #throws AccessDeniedException if user does not have access to this function
* #throws UnauthenticatedException if user is not authenticated
*/
public ZipOutputStream build(String id, ServletOutputStream responseOutputStream) throws IOException {
try {
List<List<String[]>> csvFilesContents = buildDataLists(id);
final ZipOutputStream zos = new ZipOutputStream(responseOutputStream);
streamWriteCsvToZip("control", id, zos, csvFilesContents.remove(0));
streamWriteCsvToZip("roles", id, zos, csvFilesContents.remove(0));
streamWriteCsvToZip("accounts", id, zos, csvFilesContents.remove(0));
zos.close(); // finally closing the ZipOutputStream to mark completion of ZIP file
return zos;
} catch (IOException | ClientException ex) {
throw ex;
}
}
No magic here. Just take your data and put it into the zip stream. In my case, I was pulling list/array data, dropping it into a CSV, then putting that CSV in to the zip (as an entry by using zos.putNextEntry(entry);). Both the CSVs and the ZIP are kept as streams so that nothing is written to the filesystem during this operation, and the final result can be just streamed out by the controller. Make sure to close out the entry each time you write one to the zip output stream (zos.closeEntry()).
private void streamWriteCsvToZip(String csvName, String id, ZipOutputStream zos, List<String[]> csvFileContents)
throws IOException {
String filename = id + "_" + csvName + ".csv";
ZipEntry entry = new ZipEntry(filename); // create a zip entry and add it to ZipOutputStream
zos.putNextEntry(entry);
CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(zos)); // Directly write bytes to the output stream
csvWriter.writeAll(csvFileContents); // write the contents
csvWriter.flush(); // flush the writer
zos.closeEntry(); // close the entry. Note: not closing the zos just yet as we need to add more files to our ZIP
}
You can try to send back it as a byte array:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zipOut = customService.generateZipStream();
int count;
byte data[] = new byte[2048];
BufferedInputStream entryStream = new BufferedInputStream(is, 2048);
while ((count = entryStream.read(data, 0, 2048)) != -1) {
zos.write( data, 0, count );
}
entryStream.close();
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.header("Content-Type","application/octet-stream")
.body(bos.toByteArray());
Consider that you will need to change the return type to ResponseEntity<byte[]>.
Maybe you can return ResponseEntity<Byte[]>
https://github.com/wangwei-ying/initializr/blob/main/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectGenerationController.java
line: 126
Thanks for everyone's help, I checked yours answers and solved this issue by updating export logic.
My solution:
Re-define method as customService.generateZipStream(ZipOutputStream zipOut)so that I can create a zip stream with StreamResponseBody in controller layer and then send it to service layer, in service layer, i will do export.
presudo code as below:
#PostMapping(value="/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity< StreamResponseBody > export() {
// customService.generateZipStream() is a service method that can
//generate zip file using ZipOutputStream and then return this stream
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.body(outputStream -> {
// Use inner implement and set StreamResponseBody to ZipOutputStream
try(ZipOutputStream zipOut = new ZipOutputStream(outputStream)) {
customService.generateZipStream(zipOut);
}
});
}
customService presudo code:
public void generateZipStream(ZipOutputStream zipOut) {
// ... do export here
zipOut.putEntry(...);
zipOut.write(...);
zipOut.closeEntry();
// ... balabala
}
Hope it can help you if you have similar question.

Zip multiple objects from inputStream in Java

I have a webapp that allows users to select images and then download them. For a single image, I use HTML5's anchor download and it works beautifully. Now I need to allow them to select multiple images, and download them as a .zip file. I'm using an api to get each image as an InputStream and returning a Jersey Response.
I'm new to zipping and I'm a bit confused with how zipping with InputStream should work.
For single images, it works like so:
try {
InputStream imageInputStream = ImageStore.getImage(imageId);
if (imageInputStream == null) {
XLog.warnf("Unable to find image [%s].", imageId);
return Response.status(HttpURLConnection.HTTP_GONE).build();
}
Response.ResponseBuilder response = Response.ok(imageInputStream);
response.header("Content-Type", imageType.mimeType());
response.header("Content-Disposition", "filename=image.jpg");
return response.build();
}
It's not much, but here's the java I have so far for multiple images
public Response zipAndDownload(List<UUID> imageIds) {
try {
// TODO: instantiate zip file?
for (UUID imageId : imageIds) {
InputStream imageInputStream = ImageStore.getImage(imageId);
// TODO: add image to zip file (ZipEntry?)
}
// TODO: return zip file
}
...
}
I just don't know how to deal with multiple InputStreams, and it seems that I shouldn't have multiple, right?
An InputStream per image is ok. To zip the files you need to create a .zip file for them to live in and get a ZipOutputStream to write to it:
File zipFile = new File("/path/to/your/zipFile.zip");
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
For each image, create a new ZipEntry, add it to the ZipOutputSteam, then copy the bytes from your image's InputStream to the ZipOutputStream:
ZipEntry ze = new ZipEntry("PrettyPicture1.jpg");
zos.putNextEntry(ze);
byte[] bytes = new byte[1024];
int count = imageInputStream.read(bytes);
while (count > -1)
{
zos.write(bytes, 0, count);
count = imageInputStream.read(bytes);
}
imageInputStream.close();
zos.closeEntry();
After you add all the entries, close the ZipOutputStream:
zos.close();
Now your zipFile points to a zip file full of pictures you can do whatever you want with. You can return it like you do with a single image:
BufferedInputStream zipFileInputStream = new BufferedInputStream(new FileInputStream(zipFile));
Response.ResponseBuilder response = Response.ok(zipFileInputStream);
But the content type and disposition are different:
response.header("Content-Type", MediaType.APPLICATION_OCTET_STREAM_TYPE);
response.header("Content-Disposition", "attachment; filename=zipFile.zip");
Note: You can use the copy method from Guava's ByteStreams helper to copy the streams instead of copying the bytes manually. Simply replace the while loop and the 2 lines before it with this line:
ByteStreams.copy(imageInputStream, zos);

Java copying a file out of a jar

I am trying to copy a file (Base.jar) to the same directory as the running jar file
I keep getting a corrupted jar file, that still holds the correct class structure when opened with winrar. What am I doing wrong? (I have also tried without the ZipInputStream, but that was no help) the byte[] is 20480 because that is size of it on the disk.
my code:
private static void getBaseFile() throws IOException
{
InputStream input = Resource.class.getResourceAsStream("Base.jar");
ZipInputStream zis = new ZipInputStream(input);
byte[] b = new byte[20480];
try {
zis.read(b);
} catch (IOException e) {
}
File dest = new File("Base.jar");
FileOutputStream fos = new FileOutputStream(dest);
fos.write(b);
fos.close();
input.close();
}
InputStream input = Resource.class.getResourceAsStream("Base.jar");
File fileOut = new File("your lib path");
OutputStream out = FileUtils.openOutputStream(fileOut);
IOUtils.copy(in, out);
in.close();
out.close();
and handle exceptions
No need to use ZipInputStream, unless you want to unzip the contents into memory and read.
Just use BufferedInputStream(InputStream) or BufferedReader(InputStreamReader(InputStream)).
did some more googling found this: (Convert InputStream to byte array in Java) worked for me
InputStream is = ...
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
(it looks very simular to the src for IOUtils.copy())
ZipInputStream is for reading files in the ZIP file format by entry. You need to copy the whole file (resource) that is you need to simply copy all bytes from InputStream no matter what format is. The best way to do it in Java 7 is this:
Files.copy(inputStream, targetPath, optionalCopyOptions);
see API for details

What should be the content type to download any file format in jsp?

I want to make a provision to download all file types...Is there any way to download any file format in jsp...
My code snippet:
String filename = (String) request.getAttribute("fileName");
response.setContentType("APPLICATION/OCTET-STREAM");
String disHeader = "Attachment";
response.setHeader("Content-Disposition", disHeader);
// transfer the file byte-by-byte to the response object
File fileToDownload = new File(filename);
response.setContentLength((int) fileToDownload.length());
FileInputStream fileInputStream = new FileInputStream(fileToDownload);
int i = 0;
while ((i = fileInputStream.read()) != -1) {
out.write(i);
}
fileInputStream.close();
If I specify setContentType as APPLICATION/OCTET-STREAM, pdf, text, doc files are getting downloaded.... But the problem is with image files...
What is problem with image files? I want to download all image file types...
I searched similar questions but could not find proper answer...
Thanks...
Finally I somehow managed to do this...
The problem is with JSP's "Out.write", which is not capable of writing byte stream...
I replaced jsp file with servlet...
The code snippet is:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String filename = (String) request.getAttribute("fileName");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename="+filename);
File file = new File(filename);
FileInputStream fileIn = new FileInputStream(file);
ServletOutputStream out = response.getOutputStream();
byte[] outputByte = new byte[(int)file.length()];
//copy binary contect to output stream
while(fileIn.read(outputByte, 0, (int)file.length()) != -1)
{
out.write(outputByte, 0, (int)file.length());
}
}
Now I can download all types of files....
Thanks for the responces :)
Check the following link ,
JSP download - application/octet-stream
Might help you to resolve the issue.
for images you should use setContentType(image/jpg).you can checkout this link for mime types
http://webdesign.about.com/od/multimedia/a/mime-types-by-content-type.htm

Categories