I`m trying to compress a list of Xml converted on Strings, save them in only one zip file and returning as a body of a POST on restful. But everytime I save the file I get the error "The archive is either in unknown format or damaged".
protected ByteArrayOutputStream zip(Map<String, String> mapConvertedXml) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
try {
for(Map.Entry<String, String> current : mapConvertedXml.entrySet()){
ZipEntry entry = new ZipEntry(current.getKey() + ".xml");
entry.setSize(current.getValue().length());
zos.putNextEntry(entry);
zos.write(current.getValue().getBytes());
zos.flush();
}
zos.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
return baos;
}
Can anyone help me with that?
To test your zip file please save it temporarily in your filesystem and open it manualy.
FileOutputStream fos = new FileOutputStream(pathToZipFile);
ZipOutputStream zos = new ZipOutputStream(fos);
Then, you can use something like that to construct your Rest Server and return your file.
public static Response getFileLast(#Context UriInfo ui, #Context HttpHeaders hh) {
Response response = null;
byte[] sucess = null;
ByteArrayOutputStream baos = getYourFile();
sucess = baos.toByteArray();
if(sucess!=null) {
response = Response.ok(sucess, MediaType.APPLICATION_OCTET_STREAM).header("content-disposition","attachment; filename = YourFileName").build();
} else {
response = Response.status(Status.NOT_FOUND).type(MediaType.APPLICATION_JSON).entity(new Error("Error downloading file.")).build();
}
return response;
}
You can test this Advanced Rest Client for example.
Related
I have a web project that
pulls data from Oracle DB
creates an XML file out of that data using a HashMap
ZIP it in memory
let users download the ZIP file.
note that I will not create a physical file prior to download.
I am done with 1-3. I can't seem to find a solution for the download part. I am using pure Spring MVC (as much as I can), Hibernate, MySQL.
HomeController.java
#RequestMapping(value="/doretrieve", method=RequestMethod.POST, produces="application/zip")
#ResponseBody
public ZipOutputStream doRetrieve(#RequestParam(value="calcgrouplist") String selectedCalcGroups, #RequestParam(value="env") String currentEnv){
ZipOutputStream zipCalgGroups = null;
try {
String[] cgs = calcGroupService.insertToArray(selectedCalcGroups);
for(String cg:cgs){
System.out.println("Calculation Group: " + cg);
}
Map startRetrieve = calcGroupService.startRetrieve(currentEnv, cgs);
if (startRetrieve != null ){
zipCalgGroups = calcGroupService.zipCalcGroups(currentEnv, startRetrieve);
} else {
return null;
}
} catch (NullPointerException e) {
e.printStackTrace();
}
return zipCalgGroups;
}
CalcGroupService.java
code to create zip file of the xml file/s
public ZipOutputStream zipCalcGroups(String database, Map startRetrieve) {
//Sort
//SortCalcGroupParameters sort = new SortCalcGroupParameters();
//sort.run(new File("\\" + database));
Map<String, byte[]> mapXmlFiles = startRetrieve;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos))
{
for (Map.Entry<String, byte[]> mapXmlFile:mapXmlFiles.entrySet()){
ZipEntry entry = new ZipEntry(mapXmlFile.getKey());
zos.putNextEntry(entry);
zos.write(mapXmlFile.getValue());
zos.closeEntry();
}
return zos;
} catch (IOException e) {
e.printStackTrace();
}
return null;
I was able to solve my own problem. Below are the edited methods:
HomeController:
#RequestMapping(value="/doretrieve", method=RequestMethod.POST, produces="application/zip")
#ResponseBody
public byte[] doRetrieve(HttpServletResponse response, #RequestParam(value="calcgrouplist")
String selectedCalcGroups, #RequestParam(value="env") String currentEnv){
try {
String[] cgs = calcGroupService.insertToArray(selectedCalcGroups);
for(String cg:cgs){
System.out.println("Calculation Group: " + cg);
}
//returns map of file name and xml
Map startRetrieve = calcGroupService.startRetrieve(currentEnv, cgs);
//set file name of the zipped calc group/s using selected environment
response.setHeader("Content-Disposition", "attachment; filename=" + currentEnv + ".zip");
if (startRetrieve != null ){
byte[] zipped = calcGroupService.zipCalcGroupsFromMemory(currentEnv, startRetrieve);
return zipped;
} else {
return null;
}
} catch (NullPointerException e) {
e.printStackTrace();
}
return null;
}
CalcGroupService:
public byte[] zipCalcGroupsFromMemory(String database, Map startRetrieve) {
Map<String, byte[]> mapXmlFiles = startRetrieve;
HttpServletRequest request = null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos)) {
for (Map.Entry<String, byte[]> mapXmlFile : mapXmlFiles.entrySet()) {
byte[] xml = sortCalcGroup(mapXmlFile.getKey(), mapXmlFile.getValue());
zos.putNextEntry(new ZipEntry(mapXmlFile.getKey()));
//zos.write(mapXmlFile.getValue());
zos.write(xml);
zos.closeEntry();
}
zos.close();
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
The above code produces a nice zipped xml file/s.
I am currently working on JAX-RS, where I'm trying to send and receive Zip files. Currently for sending the zip file in response, I'm able to achieve it using the below code (verified the zip downloading by entering the URL in the browser), but I'm not clear about writing the code logic to read the Zip file from response.
Please help me to achieve this functionality.
#GET
#Produces({"application/zip"})
#Path("getProduct")
public Response getProduct() {
String METHODNAME = "getProduct";
if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) {
LOGGER.entering(CLASSNAME, METHODNAME);
}
File file;
try {
Properties prop = getProp();
file = new File(prop.getProperty("ZipLocation")+prop.getProperty("ProductZip"));
byte[] buffer = new byte[5120];
FileOutputStream fos = new FileOutputStream(file);
ZipOutputStream zos = new ZipOutputStream(fos);
ZipEntry ze= new ZipEntry(prop.getProperty("ProductOutfile"));
zos.putNextEntry(ze);
FileInputStream in = new FileInputStream("C:\\Documents\\ProductExtract.xml");
int len;
while ((len = in.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
in.close();
zos.closeEntry();
zos.close();
} catch (FileNotFoundException ex) {
LOGGER.logp(Level.SEVERE, CLASSNAME, METHODNAME, ex.getMessage(), ex);
return Response.status(204).entity(ex.getMessage()).build();
} catch (Exception ex) {
LOGGER.logp(Level.SEVERE, CLASSNAME, METHODNAME, ex.getMessage(), ex);
return Response.status(204).entity(ex.getMessage()).build();
}
return Response.ok(file, "application/zip").header("Content-Disposition", "attachment; filename=\""+file.getName()+"\"")
.header("Content-Type", "application/zip").header("Set-Cookie", "fileDownload=true; path=/").build();
}
Kindly let me know how can I achieve reading the zip file from response of the above code.
Once you are using the JAX-RS Client API, your client code can be like:
Client client = ClientBuilder.newClient();
InputStream is = client.target("http://localhost:8080")
.path("api").path("getProduct")
.request().accept("application/zip")
.get(InputStream.class);
ZipInputStream zis = new ZipInputStream(is);
Then read the content of the ZipInputStream.
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.
I want to compress the dynamic created content and write to ServletOutputStream directly, without saving it as a file on the server before compressing.
For example, I created an Excel Workbook and a StringBuffer that includes strings with the SQL template. I don't want to save the dynamic content to .xlsx and .sql file on the server before zipping the files and writing to ServletOutputStream for downloading.
Sample Code:
ServletOutputStream out = response.getOutputStream();
workbook.write(byteArrayOutputStream);
zipIt(byteArrayOutputStream,out);
public static boolean zipIt(ByteArrayOutputStream input, ServletOutputStream output) {
try {
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(output));
ZipEntry zipEntry = new ZipEntry("test.xlsx");
zos.putNextEntry(zipEntry);
if (input != null) {
zipEntry.setSize(input.size());
zos.write(input.toByteArray());
zos.closeEntry();
}
} catch (IOException e) {
logger.error("error {}", e);
return false;
}
return true;
}
Create an HttpServlet and in the doGet() or doPost() method create a ZipOutputStream initialized with the ServletOutputStream and write directly to it:
resp.setContentType("application/zip");
// Indicate that a file is being sent back:
resp.setHeader("Content-Disposition", "attachment;filename=test.zip");
// workbook.write() closes the stream, so we first have to
// write it to a "buffer", a ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
workbook.write(baos);
byte[] data = baos.toByteArray();
try (ZipOutputStream out = new ZipOutputStream(resp.getOutputStream())) {
// Here you can add your content to the zip
ZipEntry e = new ZipEntry("test.xlsx");
// Configure the zip entry, the properties of the file
e.setSize(data.length);
e.setTime(System.currentTimeMillis());
// etc.
out.putNextEntry(e);
// And the content of the XLSX:
out.write(data);
out.closeEntry();
// You may add other files here if you want to
out.finish();
} catch (Exception e) {
// Handle the exception
}
}
Related to this question which is about how to send a binary file to a client. I am doing this, actually my method #Produces("application/zip"), and it works well for the browser client. Now I'm trying to write some automated tests against the rest service, using the Wink client. So my question is not how to send the file to the client, but for how to consume the file, as a java rest client (in this case Apache Wink).
My resource method looks something like the below... Once I have a Wink ClientResponse object, how can I get the file from it so I can work with it?
#GET
#Path("/file")
#Produces("application/zip")
public javax.ws.rs.core.Response getFile() {
filesToZip.put("file.txt", myText);
ResponseBuilder responseBuilder = null;
javax.ws.rs.core.Response response = null;
InputStream in = null;
try {
in = new FileInputStream( createZipFile( filesToZip ) );
responseBuilder = javax.ws.rs.core.Response.ok(in, MediaType.APPLICATION_OCTET_STREAM_TYPE);
response = responseBuilder.header("content-disposition", "inline;filename="file.zip").build();
} catch( FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
return response;
The method that actually creates the zip file looks like this
private String createZipFile( Map<String,String> zipFiles ) {
ZipOutputStream zos = null;
File file = null;
String createdFileCanonicalPath = null;
try {
// create a temp file -- the ZIP Container
file = File.createTempFile("files", ".zip");
zos = new ZipOutputStream( new FileOutputStream(file));
// for each entry in the Map, create an inner zip entry
for (Iterator<Map.Entry<String, String>> it = zipFiles.entrySet().iterator(); it.hasNext();){
Map.Entry<String, String> entry = it.next();
String innerFileName = entry.getKey();
String textContent = entry.getValue();
zos.putNextEntry( new ZipEntry(innerFileName) );
StringBuilder sb = new StringBuilder();
byte[] contentInBytes = sb.append(textContent).toString().getBytes();
zos.write(contentInBytes, 0, contentInBytes.length);
zos.closeEntry();
}
zos.flush();
zos.close();
createdFileCanonicalPath = file.getCanonicalPath();
} catch (SecurityException se) {
se.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (zos != null) {
zos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return createdFileCanonicalPath;
}
You can consume it simply as input stream and use ZipInputStream to unzip it.
Here's example using Apache HTTP Client:
HttpClient httpclient = new DefaultHttpClient();
HttpGet get = new HttpGet(url);
get.addHeader(new BasicHeader("Accept", "application/zip"));
HttpResponse response = httpclient.execute(get);
InputStream is = response.getEntity().getContent();
ZipInputStream zip = new ZipInputStream(is);