Coming Up for Air

JSF and File Downloads

At IEC, we have an application used to report inventory counts. Part of the app creates an Excel spreadsheet using POI. The user selects a batch from a select/combo, click on the button, and the server sends them a spreadsheet. The basic work flow is this:

  1. Display the page

  2. User selects a batch and clicks the button

  3. JSF calls the specified action on the backing

  4. The backing bean creates the spreadsheet, then navigates to success.jsp, a plain ol' JSP

  5. The JSP pulls the backing bean from the session, gets the workbook reference from it, and streams that to the user using a ServletOutputStream.

This works well under Tomcat and JSF 1.1. Our goal, though, is to migrate to a full JEE 5 environment, hosted on Glassfish. Glassfish, though, ships with JSF 1.2, as JSF is now part of the JEE spec (starting with version 5). This poses a problem, though, as there has apparently been a good deal of work in view handling in 1.2 that breaks this behavior. Ed Burns has been nice enough to take a look for me, but, while waiting for him to get time to help me, I took a stab at fixing it. My tentative work around for it is as follows:

  • Alter the form to use a "normal" (read as: non-JSF) form and submit to a servlet.

  • Write a servlet that in effect duplicates the JSF lifecycle:

    • Build/load the FacesContext

    • Get a reference to the backing bean

    • Pull the batch id from the request and set it on the backing bean.

    • Call the action method.

At this point, the servlet works the same as the JSP:

  • Get the reference to the workbook

  • Stream the workbook to the user.

This works, but is a pretty ugly hack. In fact, check out the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class ExcelServlet extends HttpServlet {
    /**
     * If you examine FacesContext, you'll find that the setFacesContextAsCurrentInstance method is protected.
     */
    private abstract static class ProtectedFacesContext extends FacesContext {
        protected static void setFacesContextAsCurrentInstance(FacesContext facesContext) {
                FacesContext.setCurrentInstance(facesContext);
        }
    }
    public ExcelServlet() {
        super();
    }
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
        this.getFacesContext(request, response);
        BatchAuditReportBean report = (BatchAuditReportBean) FacesUtils.getManagedBean("batchAuditReportBean");
        report.performTask();
        org.apache.poi.hssf.usermodel.HSSFWorkbook workBook = report
                        .getWorkBook();
        ServletOutputStream sos;
        try {
                response.setHeader("Cache-Control", "max-age=1");
                response.setHeader("Content-Disposition",
                                "attachment; filename=\"InventoryErrorReportServlet.xls\"");
                response.setContentType("application/vnd.ms-excel");
                sos = response.getOutputStream();
                workBook.write(sos);
                sos.flush();
        } catch (IOException ioe) {
                System.out.println("IO Exception: " + ioe.toString());
        }
        response.getOutputStream().close();
    }
    private FacesContext getFacesContext(ServletRequest req,
                ServletResponse res) {
        /** Try to get it first */
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (facesContext != null)
                return facesContext;
        // Use the FactoryFinder to grab the Lifecycle object
        FacesContextFactory contextFactory = (FacesContextFactory)
        FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
        LifecycleFactory lifecycleFactory = (LifecycleFactory)
        FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
        Lifecycle lifecycle =
                lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
        // Here's where the ProtectedFacesContext comes in
        facesContext = contextFactory.getFacesContext(this.getServletContext(),
                        req, res, lifecycle);
        ProtectedFacesContext.setFacesContextAsCurrentInstance(facesContext);
        return facesContext;
    }
}

This code doesn’t yet have the parameter fetching, but you can see how unattractive it is. Hopefully, Ed will have a nicer fix for me. Until then, I may have to put this monstrosity in production.

Quotes

Sample quote

Quote source