4

I need to find a way to perform a page navigation after generating a file download. So far, I've got the file download ready and working:

FileInputStream stream = new FileInputStream(file);
        FacesContext fc = FacesContext.getCurrentInstance();
        ExternalContext ec = fc.getExternalContext();
        ec.responseReset();
        ec.setResponseContentType("application/octet-stream");
        ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");

        OutputStream out = ec.getResponseOutputStream();

        byte[] outputByte = new byte[4096];

        while(stream.read(outputByte, 0, 4096) != -1)
        {
                out.write(outputByte, 0, 4096);
        }
        stream.close();
        out.flush();
        out.close();
        fc.responseComplete();

So far, I've try redirecting from the ExternalContext afterwards but i get an IllegalStateException.

ec.redirect(url)

I've also tried wrapping all the previous code in a string method that returns the page to be navigated at the end. That didn't work either.

Any recommendations?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Roberto Betancourt
  • 2,375
  • 3
  • 27
  • 35
  • The last time I had to do something similar I ended by delegating the file download to a plain old servlet, and calling it from javascript `window.open`. Maybe there is a more elegant way to do it. – SJuan76 Jun 05 '13 at 02:09
  • Well I've been at it the whole day. I dont' really know if there are any. – Roberto Betancourt Jun 05 '13 at 02:20
  • @dustedrob 2 suggestions: get rid of the `responseReset()` and replace it with the `responseComplete` and then either call `fc.getApplication().getNavigationHandler().handleNavigation(fc,from,destinationPage)` or `ec.dispatch(url)` – kolossus Jun 05 '13 at 04:57
  • @kolossus OP's using `fc.responseComplete` after completing the response with the file to download. If you complete the response before, you will get errors. – Luiggi Mendoza Jun 05 '13 at 04:59
  • AFAIK you can't redirect after downloading the file in a clean way. Your best bet would be doing a redirect after executing the form submission. Another way would be the example from @SJuan76. – Luiggi Mendoza Jun 05 '13 at 05:00
  • @LuiggiMendoza OP's current `responseComplete` may be coming too late to make a difference. The `responseComplete` method I believe is just a signal to the JSF runtime that the response has been rendered and that JSF should jump straight to the Restore view phase. It is actually a prerequisite for filedownload operation. OP's use of `responseReset` however is a signal to the `ServletContext` to actually reset the response stream and may be directly responsible for the problems now. It's a theory. Let's see him try it – kolossus Jun 05 '13 at 05:03
  • @kolossus the problem is that after you execute `fc.responseComplete` you must not modify the response by any means. A redirect action will, of course, trigger modifications for the response, thus giving errors. Code prior to `fc.responseComplete` is ok. – Luiggi Mendoza Jun 05 '13 at 05:04
  • @LuiggiMendoza hmmm, does delegating the navigation to the navhandler count as modifying the response? – kolossus Jun 05 '13 at 05:08
  • @kolossus yes it does. – Luiggi Mendoza Jun 05 '13 at 05:09

2 Answers2

7

You can't return 2 responses to 1 request. You can return only 1 response to 1 request. A file download counts as one response and a redirect counts as another response.

Your best bet is to return a response which automatically initiates a new request in some way. The second response can then be returned to this automatically initiated request. JavaScript is very helpful in this with functions like window.location (to fire a new request in current window), window.open() (to fire a new request in a new window), and form.submit() (to submit a POST form).

The simplest way would be to redirect to the target page wherein some JavaScript is conditionally rendered (and immediately executed) which in turn triggers the file download by e.g. window.location or form.submit(). The window.open() is inappropriate if the download itself is already set as an attachment. Note that this approach doesn't redirect after the user has saved the file download, this is plain impossible as there's no client side event to hook on when the last bit of the download is saved. Instead, the redirect is first performed and then the file download.

See also:

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
1

Thanks to @BalusC I did it! From the first page i just perform the navigation and on the landing page i included the following code:

           <c:if test="#{bean.readyForDownload}">
                <script>
                window.onload = function() {
                    document.getElementById('form:link').onclick();
                }
                </script>


            <h:form id="form">
                <h:commandLink id="link" action="#{bean.prepareFileDownload()}"/>
            </h:form>
           </c:if>
Roberto Betancourt
  • 2,375
  • 3
  • 27
  • 35
  • 1
    Could you explain with more details how were you able to direct the user to the pdf after downloading the file? – Erick May 24 '16 at 11:36