使用JSF 2和XSLT导出到Excel [英] Export to Excel using JSF 2 and XSLT

查看:111
本文介绍了使用JSF 2和XSLT导出到Excel的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们使用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中正确打开,但是最好是用户可以避免这种解决方法.

We used Java 5, Tomcat 5, Xalan, and JSF 1 to build an application that used XSLT, XML, and a Tomcat Filter to enable users to export their data in Excel format. We recently upgraded to Java 1.7.0_07, Tomcat 7.022 and JSF 2.1 (jsf-api-2.1.0-b03.jar). Due to the effort involved we have not yet upgraded to facelets; we still use jsp's. We use an tag to display the Excel report in its own popup window. The problem is that after the upgrade the popup is now displaying raw xml in IE, rather than the popup opening in Excel directly. The raw xml can be saved from the browser to a file, and if that saved file is double clicked, it does open up in Excel correctly, but it would be best if users could avoid that work-around.

我相信问题在于,现在JSF 2中的响应早于JSF 1中的响应.我们的web.xml文件为Tomcat定义了以下过滤器:

I believe that the problem is that the response in JSF 2 is now being committed earlier than it was in JSF 1. Our web.xml file defines the following filters for 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类包含以下几行:

And our XsltProcesserFilter class contains the following lines:

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

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

By using sysouts, I determined that the contentType is not being set under JSF 2, presumably because the response has already been committed. I have tried setting the contentType in the jsp that outputs the xml, but JSF then throws many errors, so presumably I need to set it later in the process (like in the filter above). I have tried response.setBufferSize(6400000) prior to the doFilter in the XsltProcessorFilter, since I have read that doing so might delay the commit, but that does not solve the problem either.

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

How can I set the contentType to application/vnd.ms-excel after faces has completed its processing but before the commit so that the browser will open up into Excel?

推荐答案

上述问题的解决方案涉及两个问题.第一个问题是Tomcat在返回XSLTProcessorFilter之前先刷新缓冲区并提交响应.通过在将控制权移交给Faces之前在XSLTProcessorFilter中将缓冲区大小设置为较大的值,可以解决此问题.接下来,Faces类JspViewHandlingStrategy在两点上刷新了输出.通过在XSLTProcessorFilter中添加值为"true"的isExcelXML请求属性来解决此问题.然后,在JspViewHandlingStrategy中添加了编码以检查isXML属性,如果其值为true,则跳过刷新.进行这些更改之后,现在将以所需的格式向用户显示Excel窗口.当然,可以简单地在JspViewHandlingStrategy中注释掉这两次刷新,但是大概它们有一定用途,因此,这里的解决方案仅在它们引起手头的问题时(调用XSLTProcessorFilter时)才绕过它们.

The solution to the above problem involved two issues. The first issue was that Tomcat was flushing the buffer and committing the response prior to the return to XSLTProcessorFilter. This was overcome by setting the buffer size to a large value in XSLTProcessorFilter prior to handing control over to Faces. Next, the Faces class JspViewHandlingStrategy was flushing the output at two points. This was overcome by adding a request attribute of isExcelXML with a value of "true" from XSLTProcessorFilter. Then, in JspViewHandlingStrategy coding was added to check for the isXML attribute, and if its value was true, the flushing was bypassed. After these changes, the Excel window now is presented to the user with the desired formatting. Of course one could simply comment out the two flushes in JspViewHandlingStrategy, but presumably they serve some purpose, so the solution here only bypasses them if they cause the problem at hand (when the XSLTProcessorFilter is called).

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

The doFilter method of our XSLTProcessorFilter now contains fixes:

    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类的更改部分位于其公共void renderView(FacesContext context,UIViewRoot view)方法的结尾:

The altered part of JspViewHandlingStrategy class is at the end of it's public void renderView(FacesContext context, UIViewRoot view) method:

//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();
}

由于花了数周的时间来解决,因此可能值得包括一些有关如何调试的详细信息,以防其他人可能会使用所使用的技术.

Since this took weeks to solve, it may be worthwhile to include some detail on how this was debugged in case others may have a use for the technique used.

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

Debug started with a download of the JSF source code. System.out,println() statements were put in each source code file starting with FacesServlet.java in its service method. The goal of the debug was to see where the isCommitted boolean switched from "false" to "true" since that was what was causing the problem. Statements similar to the following were used throughout each analyzed source code file:

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

(请注意,"import javax.servlet.ServletResponse;"通常必须添加到源文件中的import语句中.)一旦找到一行,其中isCommitted值在它之前为false,在其后为true,则调试工作切换到该行调用的实例化类.继续进行此过程,直到找到有问题的缓冲区刷新行为止.

(Note that "import javax.servlet.ServletResponse;" typically had to be added to the import statements in the source file.) Once a line was found where the isCommitted value was false before it and true after it, then the debug effort switched to the instantiated class that was called by that line. This process was continued until the problematic buffer flush lines were found.

当然,刚刚描述的更改必须从项目中运行才能发现问题.必须编译每个源文件(其类路径包含最终项目的许多jar).编译该类后,原始的face jars被重命名为zip文件,新覆盖的类通过覆盖该文件的版本而放置在zip文件中.然后,将zip文件重命名为jar.然后将jar放入Eclipse,重新编译项目,然后运行项目.在Tomcat输出窗口上观察到输出.当一个编译产生多个类时(内部类可能会发生这种情况),则所有新编译的类都将放置在它们的位置. (尽管可能不需要这样做.)

Of course, the changes just described had to be run from the project to find the problem. Each source file had to be compiled (with a classpath that contained the many jars of the end project). Once the class was compiled, the original faces jar was renamed as a zip file and the newly compiled class put inside the zip file by overwriting the version of that file that was already there. Then the zip file was renamed back to a jar. The jar was then put into Eclipse, the project recompiled, and the project was run. Output was observed on the Tomcat output window. When multiple classes resulted from a single compilation (as can occur with inner classes) then all newly compiled classes were put in their place. (Although this may not have been required.)

这篇关于使用JSF 2和XSLT导出到Excel的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆