views:

249

answers:

2

I'm trying to get Tomcat to write out the servlet contents as a bzip2 file (Silly requirement perhaps but it's apparently necessary for some integration work). I'm using the Spring framework so this is in an AbstractController.

I'm using the bzip2 library from http://www.kohsuke.org/bzip2/

I can get the contents bzipped fine but when the file is written out it seems to contain a bunch of meta data and is unrecognisable as a bzip2 file.

Here's what I'm doing

// get the contents of my file as a byte array
byte[] fileData =  file.getStoredFile();

ByteArrayOutputStream baos = new ByteArrayOutputStream();

//create a bzip2 output stream to the byte output and write the file data to it             
CBZip2OutputStream bzip = null;
try {
     bzip = new CBZip2OutputStream(baos);
     bzip.write(fileData, 0, fileData.length);
     bzip.close();  
} catch (IOException ex) {
     ex.printStackTrace();
}
byte[] bzippedOutput = baos.toByteArray();
System.out.println("bzipcompress_output:\t" + bzippedOutput.length);

//now write the byte output to the servlet output
//setting content disposition means the file is downloaded rather than displayed
int outputLength = bzippedOutput.length;
String fileName = file.getFileIdentifier();
response.setBufferSize(outputLength);
response.setContentLength(outputLength);
response.setContentType("application/x-bzip2");
response.setHeader("Content-Disposition",
                                       "attachment; filename="+fileName+";)");

This is being called from the following method in the Spring abstractcontroller

protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)  throws Exception

I've taken a few stabs at it in different approaches, including writing directly to the ServletOutput but I'm pretty stumped and can't find any/many examples online.

Any advice from anyone who's come across this before would be much appreciated. Alternative libraries/approaches are fine, but unfortunately it must be bzip2'd.

+3  A: 

The posted approach is indeed weird. I've rewritten so that it makes more sense. Give it a try.

String fileName = file.getFileIdentifier();
byte[] fileData = file.getStoredFile(); // BTW: Any chance to get this as InputStream? This is namely memory hogging.

response.setContentType("application/x-bzip2");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");

OutputStream output = null;

try {
     output = new CBZip2OutputStream(response.getOutputStream());
     output.write(fileData);
} finally {
     output.close();
}

You see, just wrap the output stream of the response with CBZip2OutputStream and write the byte[] to it.

You may happen to see IllegalStateException: Response already committed coming after this in the server logs (with the download being correctly sent by the way), then it means that Spring is trying to forward the request/response afterwards. I don't do Spring, so I can't go in detail, but you should at least instruct Spring to stay away from the response. Do not let it do a mapping, forward or whatever. I think returning null suffices.

BalusC
+1 for the early warning of a possible IllegalStateException.
Tim R
+2  A: 

You might find working with CompressorStreamFactory from commons-compress a little easier. It's a decedent of the Ant version you're already working with and ends up 2 lines different from BalusC's example.

More or less a matter of library preference.

OutputStream out = null;
try {
    out = new CompressorStreamFactory().createCompressorOutputStream("bzip2", response.getOutputStream());
    IOUtils.copy(new FileInputStream(input), out); // assuming you have access to a File.
} finally {
    out.close();
}
Tim R