Coming Up for Air

JSF and File Downloads

Tuesday, April 04, 2006 |

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.

Search

    Quotes

    Sample quote

    Quote source

    About

    My name is Jason Lee. I am a software developer living in the middle of Oklahoma. I’ve been a professional developer since 1997, using a variety of languages, including Java, Javascript, PHP, Python, Delphi, and even a bit of C#. I currently work for Red Hat on the WildFly/EAP team, where, among other things, I maintain integrations for some MicroProfile specs, OpenTelemetry, Micrometer, Jakarta Faces, and Bean Validation. (Full resume here. LinkedIn profile)

    I am the president of the Oklahoma City JUG, and an occasional speaker at the JUG and a variety of technical conferences.

    On the personal side, I’m active in my church, and enjoy bass guitar, running, fishing, and a variety of martial arts. I’m also married to a beautiful woman, and have two boys, who, thankfully, look like their mother.

    My Links

    Publications