views:

172

answers:

2

As near as I can tell, this falls under a "you can't do that". I'm about to re-think my solution and work around it, but I thought that asking here was worth at least a shot.

My JSP/Spring/Struts Servlet produces reports, writes them to PDF, and queues them for download, then waits for the user to request more reports. The details aren't too important. I stream the PDF to the server with the following function call:

public static void streamFileToBrowser(File docFile, HttpServletResponse response) throws Exception {
try {
    // return file to user
    // set contextType
    response.setContentType("application/pdf");
    // setting some response headers
    response.setHeader("Expires", "0");
    response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
    response.setHeader("Pragma", "public");
    response.setHeader("Content-Disposition","attachment; filename=" + docFile.getName() );

    ServletOutputStream outs = response.getOutputStream();
    byte[] outputFileBytes = util_getBytesFromFile(docFile.getAbsolutePath());
    response.setContentLength(outputFileBytes.length);
    outs.write(outputFileBytes);    // byte[]
    outs.flush();
    outs.close();
    } catch (Exception e) {
        throw e;
    }
}

That's pretty straight-forward. I use the response's ServletOutputStream object to ship my bits. It works well.

However, sometimes I trap some errors that are caused during the generation of the reports. They're informative messages like "There were no orders from account blah-blah-blah". I have a section in my JSP that will catch and display these.

So here's the rub: When I have no report to send, I don't call the above function. But I always call something like:

return mapping.findForward("pullReports");

as the last line in my ActionForward method and the errors appear. However, if I have bits to send via the streamFileToBrowser() function, my eventual call to mapping.findForward does nothing.

A bit of digging tells me that Struts is only able to handle one response to the HttpServletResponse object at a time. I've used it with my call to streamFileToBrowser(), so the eventual call to mapping.findForward(...) does nothing as far as my client is concerned.

Anyone else run into this and found a solution?

+3  A: 

What I've done is to make sure that to the extent that it's possible, all errors get caught before the PDF generation begins. That way you can send back a plain old set of form errors, or whatever. Once you've started sending back an attachment like that, the browser isn't going to pay attention to anything else. If you absolutely can't find errors before you get into generating the PDF file, I think the only thing you can do is to embed the error messages in the PDF itself (unpleasant, I know).

Pointy
That's pretty much the direction I was headed. The error messages then become something like "don't do that" and demand the user clear up their request before obtaining their report. Not quite what I was going for, but it meets the specification that I alert the user as to why there is data missing from the report that they tried to create.
Berin
+1  A: 

Just rearrange the code so to move up the util thing to front in its own try-catch block and keep the response untouched until the try block is passed.

byte[] outputFileBytes;

try {
    outputFileBytes = util_getBytesFromFile(docFile.getAbsolutePath());
} catch (Exception e) {
    throw e; // Do the forward call when this is caught in the calling method. If necessary, wrap in a more specific excaption so that you can distinguish what action to take.
}

response.setContentType("application/pdf");
response.setHeader("Expires", "0");
response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
response.setHeader("Content-Disposition","attachment; filename=" + docFile.getName() );
response.setContentLength(outputFileBytes.length);
ServletOutputStream outs = response.getOutputStream();
outs.write(outputFileBytes);    // byte[]
outs.close();

Be aware that this is memory hogging. If you can, rather let the util thing return an InputStream (no, not a ByteArrayInputStream).

BalusC