【问题标题】:Export to Excel using JSF 2 and XSLT使用 JSF 2 和 XSLT 导出到 Excel
【发布时间】:2013-08-21 15:30:24
【问题描述】:

我们使用 Java 5、Tomcat 5、Xalan 和 JSF 1 构建了一个应用程序,该应用程序使用 XSLT、XML 和 Tomcat 过滤器使用户能够以 Excel 格式导出他们的数据。我们最近升级到 Java 1.7.0_07、Tomcat 7.022 和 JSF 2.1 (jsf-api-2.1.0-b03.jar)。由于所涉及的努力,我们尚未升级到 facelets;我们还是用jsp的。我们使用标签在其自己的弹出窗口中显示 Excel 报告。问题是升级后弹出窗口现在在 IE 中显示原始 xml,而不是直接在 Excel 中打开弹出窗口。原始 xml 可以从浏览器保存到文件中,如果双击保存的文件,它会在 Excel 中正确打开,但最好是用户可以避免这种变通方法。

我认为问题在于 JSF 2 中的响应现在比 JSF 1 更早提交。我们的 web.xml 文件为 Tomcat 定义了以下过滤器:

  <filter>
    <filter-name>XSLT Processor</filter-name>
    <filter-class>com.cs.common.jsf.util.XsltProcessorFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>XSLT Processor</filter-name>
    <url-pattern>*.xml</url-pattern>
  </filter-mapping>

  <filter>
    <filter-name>Hibernate Session Manager</filter-name>
    <filter-class>com.cs.common.hibernate.HibernateSessionServletFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>Hibernate Session Manager</filter-name>
    <url-pattern>*.faces</url-pattern>
  </filter-mapping>
  <filter-mapping>
    <filter-name>Hibernate Session Manager</filter-name>
    <url-pattern>*.xml</url-pattern>
  </filter-mapping>

  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xml</url-pattern>
  </servlet-mapping>

我们的 XsltProcesserFilter 类包含以下几行:

fChain.doFilter(request, wrapper);
response.setContentType("application/vnd.ms-excel");

通过使用sysouts,我确定contentType 没有在JSF 2 下设置,大概是因为响应已经提交。我曾尝试在输出 xml 的 jsp 中设置 contentType,但 JSF 会引发许多错误,所以大概我需要在该过程的稍后部分设置它(如上面的过滤器中)。我在XsltProcessorFilter 中的doFilter 之前尝试过response.setBufferSize(6400000),因为我已经读过这样做可能会延迟提交,但这也不能解决问题。

如何在 faces 完成处理后但在提交之前将contentType 设置为 application/vnd.ms-excel,以便浏览器打开 Excel?

【问题讨论】:

  • 进度报告 - 我已经下载了 JSF 源代码并将 sysout 语句放入以本地化更改 ContentType 的位置。然后,在更改之前我捕获现有值,并在更改之后将其设置回来。我现在让 Excel 出现,但格式不正确。我计划继续工作,直到我可以在这里发布答案。

标签: excel xslt jsf-2 tomcat7


【解决方案1】:

上述问题的解决方案涉及两个问题。第一个问题是 Tomcat 在返回到 XSLTProcessorFilter 之前刷新缓冲区并提交响应。在将控制权交给 Faces 之前,通过在 XSLTProcessorFilter 中将缓冲区大小设置为较大的值来克服这个问题。接下来,Faces 类 JspViewHandlingStrategy 在两点刷新输出。这通过从 XSLTProcessorFilter 添加值为“true”的 isExcelXML 请求属性来克服。然后,在 JspViewHandlingStrategy 中添加编码以检查 isXML 属性,如果其值为 true,则绕过刷新。在这些更改之后,Excel 窗口现在以所需的格式呈现给用户。当然,可以简单地注释掉 JspViewHandlingStrategy 中的两个刷新,但推测它们有某种用途,所以这里的解决方案只有在它们引起手头问题时才绕过它们(当调用 XSLTProcessorFilter 时)。

我们的 XSLTProcessorFilter 的 doFilter 方法现在包含修复:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain fChain) throws IOException, ServletException {
        String xslTemplatePath = request.getParameter(XSLT_REQUEST_PARAM);
        File xslTemplate = (xslTemplatePath == null) ? null : new File(servletPath, xslTemplatePath);

        if ((xslTemplatePath != null) && xslTemplate.exists()) {
            // Run the standard request processing to obtain the XML output
            CharacterResponseWrapper wrapper =  new CharacterResponseWrapper((HttpServletResponse) response);
            response.setBufferSize(6400000); // This line overcomes Tomcat buffer flushing
            request.setAttribute("isExcelXML", "true"); // This line signals to JSF to bypass the flushing
            fChain.doFilter(request, wrapper);
            response.setContentType("application/vnd.ms-excel");

// Transform the XML using the XSL stylesheet specified on the URL
            Source xmlSource = new StreamSource(new StringReader(wrapper.toString()));
            StreamSource xslSource = new StreamSource(xslTemplate);

            try {
                Transformer transformer = tFactory.newTransformer(xslSource);
                StreamResult out = new StreamResult(response.getWriter());              
                transformer.transform(xmlSource, out);

            } catch (Throwable t) {
                t.printStackTrace(response.getWriter());
            }

        } else { // standard processing
            fChain.doFilter(request, response);
        }
    }

JspViewHandlingStrategy 类的更改部分位于其 public void renderView(FacesContext context, UIViewRoot view) 方法的末尾:

//For XML output to Excel, bypass later flushings
boolean bypassFlush = false;
if (((ServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest()).getAttribute("isExcelXML")!=null
&& ((ServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest()).getAttribute("isExcelXML").toString().equals("true")) {
    bypassFlush = true;
}

    // write any AFTER_VIEW_CONTENT to the response (This comment in original JSF file)
    // side effect: AFTER_VIEW_CONTENT removed (This comment in original JSF file)
    ViewHandlerResponseWrapper wrapper = (ViewHandlerResponseWrapper)
          RequestStateManager.remove(context, RequestStateManager.AFTER_VIEW_CONTENT);
    if (null != wrapper && !bypassFlush) { //fix to Excel issue involved bypassing flush if isExcelXML set to true
        wrapper.flushToWriter(extContext.getResponseOutputWriter(),
                              extContext.getResponseCharacterEncoding());
    }

    if (!bypassFlush) { //fix to Excel issue involved bypassing flush if isExcelXML set to true
        extContext.responseFlushBuffer();
}

由于这需要数周时间才能解决,因此可能值得包含一些有关如何调试此问题的详细信息,以防其他人可能使用所使用的技术。

从下载 JSF 源代码开始调试。 System.out,println() 语句在其服务方法中以 FacesServlet.java 开头的每个源代码文件中放置。调试的目标是查看 isCommitted 布尔值从“false”切换到“true”的位置,因为这是导致问题的原因。在每个分析的源代码文件中都使用了类似于以下的语句:

System.out.println("someClass someLineId - " + someObjectReference.getClass().getName() +
    ((ServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).isCommitted() +
    ((ServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse()).getContentType());

(请注意,“import javax.servlet.ServletResponse;”通常必须添加到源文件中的导入语句中。)一旦发现 isCommitted 值在其之前为 false 且在其之后为 true 的行,然后调试工作切换到该行调用的实例化类。这个过程一直持续到找到有问题的缓冲区刷新行。

当然,刚才描述的更改必须从项目中运行才能发现问题。必须编译每个源文件(使用包含最终项目的许多 jar 的类路径)。一旦类被编译,原来的 faces jar 被重命名为一个 zip 文件,并且新编译的类通过覆盖已经存在的那个文件的版本放在 zip 文件中。然后将 zip 文件重命名回 jar。然后将 jar 放入 Eclipse,重新编译项目,然后运行项目。在 Tomcat 输出窗口中观察到输出。当单个编译产生多个类时(就像内部类可能发生的那样),所有新编译的类都被放置在它们的位置。 (尽管这可能不是必需的。)

【讨论】:

    猜你喜欢
    • 2010-11-06
    • 1970-01-01
    • 2016-04-16
    • 1970-01-01
    • 2013-11-03
    • 2020-12-29
    • 2014-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多