Download dynamically created zip files in play framework - java

Hi i am trying to write a play framework service where i can download multiple files. I create zip of multiple file on the fly but i am not sure how to send it as a response in Play Framework i will show what i have done so far.
public Result download() {
String[] items = request().queryString().get("items[]");
String toFilename = request().getQueryString("toFilename");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(baos))) {
for (String item : items) {
Path path = Paths.get(REPOSITORY_BASE_PATH, item);
if (Files.exists(path)) {
ZipEntry zipEntry = new ZipEntry(path.getFileName().toString());
zos.putNextEntry(zipEntry);
byte buffer[] = new byte[2048];
try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(path))) {
int bytesRead = 0;
while ((bytesRead = bis.read(buffer)) != -1) {
zos.write(buffer, 0, bytesRead);
}
} finally {
zos.closeEntry();
}
}
}
response().setHeader("Content-Type", "application/zip");
response().setHeader("Content-Disposition", "inline; filename=\"" + MimeUtility.encodeWord(toFilename) + "\"");
//I am confused here how to output the response of zip file i have created
//I tried with the `baos` and with `zos` streams but not working
return ok(baos.toByteArray());
} catch (IOException e) {
LOG.error("copy:" + e.getMessage(), e);
return ok(error(e.getMessage()).toJSONString());
}
return null;
}
i tried sending response with return ok(baos.toByteArray()); i was able to download file but when i open the downloaded file it give me error An error occurred while loading the archive.

You need to close the zip file. After adding all entries, do: zos.close()
On a side note, I would recommend writing the zip file to disk rather than keeping it in a memory buffer. You could then use return ok(File content, String filename) to send its content to the client.

I am adding this answer if someone wants to know what was the final code:
String[] items = request().queryString().get("items[]");
String toFilename = request().getQueryString("toFilename");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(baos))) {
for (String item : items) {
Path path = Paths.get(REPOSITORY_BASE_PATH, item);
if (Files.exists(path)) {
ZipEntry zipEntry = new ZipEntry(path.getFileName().toString());
zos.putNextEntry(zipEntry);
byte buffer[] = new byte[2048];
try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(path))) {
int bytesRead = 0;
while ((bytesRead = bis.read(buffer)) != -1) {
zos.write(buffer, 0, bytesRead);
}
} finally {
zos.closeEntry();
}
}
}
zos.close(); //closing the Zip
response().setHeader("Content-Type", "application/zip");
response().setHeader("Content-Disposition", "attachment; filename=\"" + MimeUtility.encodeWord(toFilename) + "\"");
return ok(baos.toByteArray());
} catch (IOException e) {
LOG.error("copy:" + e.getMessage(), e);
return ok(error(e.getMessage()).toJSONString());
}

Thank you for the help! There are two extra changes I made so it works for me in scala playframework 2.5.x
instead of return ok(baos.toByteArray()) ,
use Ok.chunked(StreamConverters.fromInputStream(fileByteData))
Instead of reading byte to byte the file,FileUtils.readFileToByteArray(file) can be very helpful here.
Attached is the complete version of my code.
import java.io.{BufferedOutputStream, ByteArrayInputStream, ByteArrayOutputStream}
import java.util.zip.{ZipEntry, ZipOutputStream}
import akka.stream.scaladsl.{StreamConverters}
import org.apache.commons.io.FileUtils
import play.api.mvc.{Action, Controller}
class HomeController extends Controller {
def single() = Action {
Ok.sendFile(
content = new java.io.File("C:\\Users\\a.csv"),
fileName = _ => "a.csv"
)
}
def zip() = Action {
Ok.chunked(StreamConverters.fromInputStream(fileByteData)).withHeaders(
CONTENT_TYPE -> "application/zip",
CONTENT_DISPOSITION -> s"attachment; filename = test.zip"
)
}
def fileByteData(): ByteArrayInputStream = {
val fileList = List(
new java.io.File("C:\\Users\\a.csv"),
new java.io.File("C:\\Users\\b.csv")
)
val baos = new ByteArrayOutputStream()
val zos = new ZipOutputStream(new BufferedOutputStream(baos))
try {
fileList.map(file => {
zos.putNextEntry(new ZipEntry(file.toPath.getFileName.toString))
zos.write(FileUtils.readFileToByteArray(file))
zos.closeEntry()
})
} finally {
zos.close()
}
new ByteArrayInputStream(baos.toByteArray)
}
}

Related

Zip file containing more than one file is sent as string. How to read the string and extract individual files in Java?

Our external application sends the zip file name and content as two strings in the response to an API call. I converted the string to bytearray and used zipinputstream to read the bytearray. If the zip file contains only one file, I am able to read the string and extract the zipped file contents into a separate string. Below is the code snippet I used to extract the file content.
If the zipped file contains more than one file and the entire zipped content is sent as a single string, how do I extract individual file contents into separate strings? How to identify the end of each file in the string?
byte[] data = Base64Util.decodeToByteArray(ZipString);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ZipInputStream zin = new ZipInputStream(new BufferedInputStream(bais));
ZipEntry ze = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[4096];
String ZippedFileName = new String();
String ZippedFileData = new String();
try
{
while((ze = zin.getNextEntry()) != null)
{
ZippedFileName = ze.getName();
while((zin.read(b,0,4096))!= -1)
{
baos.write(b,0,4096);
}
}
byte[] out = baos.toByteArray();
ZippedFileData = Base64Util.encodeToString(out);
zin.close();
bais.close();
baos.close();
}
catch(Exception Ex)
{
Ex.toString();
}
Actually I think I was presenting the base64 file in string form incorrectly. For one thing, if line feeds are present in the input, it doesn't like it. If you're working on a Unix based system, you can run the below with something like java Unzip64
"$(cat a.zip | base64 | tr -d '\n')"
The following is working fine for me
import java.util.zip.ZipInputStream;
import java.util.zip.ZipEntry;
import java.util.Base64;
import java.util.Map;
import java.util.HashMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class Unzip64 {
public static void main(String[] args) {
System.out.println(Unzip64.getEntriesAndContents(args[0]));
}
public static Map<String, String> getEntriesAndContents(String base64Zip) {
Map<String, String> result = new HashMap<>();
try {
byte[] data = Base64.getDecoder().decode(base64Zip);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
try (ZipInputStream zin = new ZipInputStream(bais)) {
ZipEntry ze = null;
byte[] b = new byte[4096];
while ((ze = zin.getNextEntry()) != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int numRead = -1;
String entryName = ze.getName();
while ((numRead = zin.read(b, 0, b.length)) > -1) {
baos.write(b, 0, numRead);
}
String entryData = new String(baos.toByteArray());
result.put(entryName, entryData);
baos = null;
zin.closeEntry();
}
}
} catch (Throwable t) {
t.printStackTrace();
}
return result;
}
}
And here's a sample to run:
UEsDBAoAAAgAAKdCJ1UAAAAAAAAAAAAAAAAHAAQAcm9vdC9hL/7KAABQSwMEFAAICAgAp0InVQAAAAAAAAAAAAAAAAwAAAByb290L2EvYS50eHRL5AIAUEsHCAeh6t0EAAAAAgAAAFBLAwQUAAgICACnQidVAAAAAAAAAAAAAAAADAAAAHJvb3QvYS9iLnR4dEviAgBQSwcIxPLH9gQAAAACAAAAUEsDBBQACAgIAKdCJ1UAAAAAAAAAAAAAAAAMAAAAcm9vdC9hL2MudHh0S+YCAFBLBwiFw9zvBAAAAAIAAABQSwECCgAKAAAIAACnQidVAAAAAAAAAAAAAAAABwAEAAAAAAAAAAAAAAAAAAAAcm9vdC9hL/7KAABQSwECFAAUAAgICACnQidVB6Hq3QQAAAACAAAADAAAAAAAAAAAAAAAAAApAAAAcm9vdC9hL2EudHh0UEsBAhQAFAAICAgAp0InVcTyx/YEAAAAAgAAAAwAAAAAAAAAAAAAAAAAZwAAAHJvb3QvYS9iLnR4dFBLAQIUABQACAgIAKdCJ1WFw9zvBAAAAAIAAAAMAAAAAAAAAAAAAAAAAKUAAAByb290L2EvYy50eHRQSwUGAAAAAAQABADnAAAA4wAAAAAA

Creating a download for zip file containing text/csv from string variables

I currently need to create a zip file for downloading. This should contain two (2) csv files that are to be created from string variables. I'm at a loss on how I should do this. My draft is below.
public #ResponseBody Object getFileV1(HttpServletRequest request, HttpServletResponse response) {
try {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=Reassigned Tickets Report " + new Date().toString() + ".zip");
String stringValue1 = "This is a test value for csv1";
String stringValue2 = "This is a test value for csv2";
InputStream is1 = new ByteArrayInputStream(stringValue1.getBytes("UTF-8"));
InputStream is2 = new ByteArrayInputStream(stringValue2.getBytes("UTF-8"));
ZipInputStream zin;
ZipEntry entry;
ZipOutputStream zout= new ZipOutputStream(response.getOutputStream());
zin = new ZipInputStream(is1);
entry = zin.getNextEntry();
zout.putNextEntry(entry);
zin = new ZipInputStream(is2);
entry = zin.getNextEntry();
zout.putNextEntry(entry);
zout.closeEntry();
zin.close();
zout.close();
response.flushBuffer();
return null;
} catch (Exception e) {
e.printStackTrace();
return e;
}
}
Obviously this is not working. Probably because I'm still a novice at this. Please bear with me.
I get a "java.lang.NullPointerException" at the line where "zout.putNextEntry" is called. Would appreciate your advice. Thank you in advance.
I solved my problem after a day of looking around. This works for me. But I'm not sure if this is the most efficient way.
public #ResponseBody Object getFileV1(HttpServletRequest request, HttpServletResponse response) {
try {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=Test Report " + new Date().toString() + ".zip");
String stringValue1 = "This is a test value for csv1";
String stringValue2 = "This is a test value for csv2";
PrintWriter writer1 = new PrintWriter(new OutputStreamWriter(new FileOutputStream("stringValue1.csv"), "UTF-8"));
writer1.print(stringValue1);
writer1.close();
PrintWriter writer2 = new PrintWriter(new OutputStreamWriter(new FileOutputStream("stringValue2.csv"), "UTF-8"));
writer2.print(stringValue2);
writer2.close();
File file1 = new File("stringValue1.csv");
File file2 = new File("stringValue2.csv");
filesToZip(response, file1, file2);
file1.delete();
file2.delete();
response.flushBuffer();
return null;
} catch (Exception e) {
e.printStackTrace();
return e;
}
}
This is the method I got from another thread with a few edits.
public static void filesToZip(HttpServletResponse response, File... files) throws IOException {
// Create a buffer for reading the files
byte[] buf = new byte[1024];
// create the ZIP file
ZipOutputStream out = new ZipOutputStream(response.getOutputStream());
// compress the files
for(int i=0; i<files.length; i++) {
FileInputStream in = new FileInputStream(files[i].getName());
// add ZIP entry to output stream
out.putNextEntry(new ZipEntry(files[i].getName()));
// transfer bytes from the file to the ZIP file
int len;
while((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
// complete the entry
out.closeEntry();
in.close();
}
// complete the ZIP file
out.close();
}
The only thing I don't love is that I had to create temporary files and delete them after processing.

Output zipped directory to ByteArrayOutputStream

I have a directory which I zip with this method:
public byte[] archiveDir(File dir) {
try(ByteArrayOutputStream bos = new ByteArrayOutputStream(); ZipOutputStream zout = new ZipOutputStream(bos)) {
zipSubDirectory("", dir, zout);
return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void zipSubDirectory(String basePath, File dir, ZipOutputStream zout) throws IOException {
byte[] buffer = new byte[4096];
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
String path = basePath + file.getName() + "/";
zout.putNextEntry(new ZipEntry(path));
zipSubDirectory(path, file, zout);
zout.closeEntry();
} else {
FileInputStream fin = new FileInputStream(file);
zout.putNextEntry(new ZipEntry(basePath + file.getName()));
int length;
while ((length = fin.read(buffer)) > 0) {
zout.write(buffer, 0, length);
}
zout.closeEntry();
fin.close();
}
}
}
I then write the bytes to servlet's output stream. But when I receive the zip file, it cannot be opened "the file has wrong format". If I output zipped contents to FileOutputStream and then send file contents to servlet's output stream it works fine. Well, this would solve my problem but in this case I would always have to delete the temporary zip file after its contents are sent to servlet's outpu stream. Is it possible to do this just in memory.
Hmm,
zipSubDirectory(path, file, zout);
zout.closeEntry();
should be:
zout.closeEntry();
zipSubDirectory(path, file, zout);
The main error seems to be that zout is not closed / flushed before toByteArray is called. Here try-with-resources was a bit devious.
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
try ((ZipOutputStream zout = new ZipOutputStream(bos)) {
zipSubDirectory("", dir, zout);
}
return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}

zip Files are not unzipping in the same folder?

Here i have folder(ZipFilesFolder) in that it consist of 10 zip files say one.zip,two.zip,three.zip..ten.zip,i'm passing file every time from this folder to zipFileToUnzip as zipFilename.I need the result in the same folder(ZipFilesFolder)i need to unzip those files and instead of one.zip,two.zip,..one,two,three folder has to visible.
public static void zipFileToUnzip(File zipFilename) throws IOException {
try {
//String destinationname = "D:\\XYZ";
byte[] buf = new byte[1024];
ZipInputStream zipinputstream = null;
ZipEntry zipentry;
zipinputstream = new ZipInputStream(new FileInputStream(zipFilename));
zipentry = zipinputstream.getNextEntry();
while (zipentry != null) {
//for each entry to be extracted
String entryName = zipentry.getName();
System.out.println("entryname " + entryName);
int n;
FileOutputStream fileoutputstream;
File newFile = new File(entryName);
String directory = newFile.getParent();
if (directory == null) {
if (newFile.isDirectory()) {
break;
}
}
fileoutputstream = new FileOutputStream(
destinationname + entryName);
while ((n = zipinputstream.read(buf, 0, 1024)) > -1) {
fileoutputstream.write(buf, 0, n);
}
fileoutputstream.close();
zipinputstream.closeEntry();
zipentry = zipinputstream.getNextEntry();
}//while
zipinputstream.close();
} catch (IOException e) {
}
}
This is my code ,but it is not working,could anybody help me,how to get desired output.
There are a couple of problems with your code:
it does not compile since destinationname is commented, but referenced when opening the FileOutputStream
IOExceptions are caught and ignored. If you throw them you would get error messages that could help you diagnose the problem
when opening the FileOutputStream, you just concatenate two strings without adding a path-separator in between.
if the file to be created is in a directory, the directory is not created and thus FileOutputStream cannot create the file.
streams are not closed when exceptions occur.
If you do not mind using guava, which simplifies life when it comes to copying streams to files, you could use this code instead:
public static void unzipFile(File zipFile) throws IOException {
File destDir = new File(zipFile.getParentFile(), Files.getNameWithoutExtension(zipFile.getName()));
try(ZipInputStream zipStream = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry zipEntry = zipStream.getNextEntry();
if(zipEntry == null) throw new IOException("Empty or no zip-file");
while(zipEntry != null) {
File destination = new File(destDir, zipEntry.getName());
if(zipEntry.isDirectory()) {
destination.mkdirs();
} else {
destination.getParentFile().mkdirs();
Files.asByteSink(destination).writeFrom(zipStream);
}
zipEntry = zipStream.getNextEntry();
}
}
}
Alternatively you might also use zip4j, see also this question.

How to convert MultipartFile into byte stream

//Or any other solution to saving multipartfile into DB.
I tried with this way but getting error.
File fileOne = new File("file.getOrignalFileName");//what should be kept inside this method
byte[] bFile = new byte[(int) fileOne.length()];
try {
FileInputStream fileInputStream = new FileInputStream(fileOne);
//convert file into array of bytes
fileInputStream.read(bFile);
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
questionDao.saveImage(bFile);
MultipartFile file;
byte [] byteArr=file.getBytes();
InputStream inputStream = new ByteArrayInputStream(byteArr);
//Start Photo Upload with Adhaar No//
if (simpleLoanDto.getPic() != null && simpleLoanDto.getAdharNo() != null) {
String ServerDirPath = globalVeriables.getAPath() + "\\";
File ServerDir = new File(ServerDirPath);
if (!ServerDir.exists()) {
ServerDir.mkdirs();
}
// Giving File operation permission for LINUX//
IOperation.setFileFolderPermission(ServerDirPath);
MultipartFile originalPic = simpleLoanDto.getPic();
byte[] ImageInByte = originalPic.getBytes();
FileOutputStream fosFor = new FileOutputStream(
new File(ServerDirPath + "\\" + simpleLoanDto.getAdharNo() + "_"+simpleLoanDto.getApplicantName()+"_.jpg"));
fosFor.write(ImageInByte);
fosFor.close();
}
//End Photo Upload with Adhaar No//

Categories