JSF 2.0使用Flying Saucer将XHTML页面转换为PDF:java.lang.IllegalStateException

时间:2022-03-10 21:10:24

I am trying to convert and export a JSF Page to PDF. I tried it the following way:

我正在尝试将JSF页面转换并导出为PDF。我尝试了以下方式:

Bean:

public void createPDF() {
    try {
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(new URL(url).toString());
        renderer.layout();
        HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
        response.reset();
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "inline; filename=\"" +PDF_FILE_NAME+ "\"");
        OutputStream browserStream = response.getOutputStream();
        renderer.createPDF(browserStream);
    } catch (Exception ex) {
        Logger.getLogger(PdfBean.class.getName()).log(Level.SEVERE, null, ex);
    }
}

Page with the Create PDF Button /home.xhtml:

使用“创建PDF”按钮/home.xhtml的页面:

<ui:define name="content">
    <center>
        <h:form id="pdfgen">
            <h:panelGrid columns="2">
                <h:outputText value="Enter Name:"/>
                <h:inputText value="#{pdfBean.name}"/>
            </h:panelGrid>
            <h:commandButton value="Create PDF" action="#{pdfBean.createPDF()}"/>
        </h:form>
    </center>
</ui:define>

The Page which I want to convert:

我要转换的页面:

<ui:define name="content">
    <center>
        <h:outputText value="Hello #{pdfBean.name}"/>
    </center>
</ui:define>

When I try that I get a PDF only once, then never again. I got following Facelet Exception:

当我尝试我只获得一次PDF时,再也不会。我得到了以下Facelet异常:

SEVERE: Error Rendering View[/home.xhtml]
java.lang.IllegalStateException: PWC3991: getOutputStream() has already been called for this response
...
WARNING: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.IllegalStateException: PWC3991: getOutputStream() has already been called for this response

What am I doing wrong?

我究竟做错了什么?

Updated Bean: see BalusC's answer:

更新Bean:请参阅BalusC的答案:

public void createPDF() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();
    String servername = externalContext.getRequestServerName();
    String port = String.valueOf(externalContext.getRequestServerPort());
    String appname = externalContext.getRequestContextPath();
    String protocol = externalContext.getRequestScheme();
    this.url = protocol + "://" + servername + ":" + port + appname + PDF_PAGE;
    try {
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(new URL(url).toString());
        renderer.layout();
        HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
        response.reset();
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "inline; filename=\"" + PDF_FILE_NAME + "\"");
        OutputStream browserStream = response.getOutputStream();
        renderer.createPDF(browserStream);

    } catch (Exception ex) {
        Logger.getLogger(PdfBean.class.getName()).log(Level.SEVERE, null, ex);
    }
    facesContext.responseComplete();
}

1 个解决方案

#1


8  

You need to instruct JSF that you've already taken the response handling in your hands and that JSF should not handle the default navigation when the action method is finished. Add this to the end of the action method:

您需要指示JSF您已经完成了响应处理,并且在操作方法完成时JSF不应该处理默认导航。将其添加到操作方法的末尾:

facesContext.responseComplete();

Update as per the comments, you're accessing ExternalContext as an instance variable which suggests that you assigned it and the FacesContext as class variable, either static or as property of a session scoped bean. This is definitely a bad idea. You should get hand of them inside the local method by FacesContext#getCurrentInstance() and never assign them as class variable. They are namely bound to a specific request thread which do not exist in next request anymore.

根据注释更新,您将访问ExternalContext作为实例变量,它建议您将它和FacesContext分配为类变量,静态或作为会话范围bean的属性。这绝对是一个坏主意。你应该通过FacesContext#getCurrentInstance()在本地方法中获取它们,并且永远不要将它们作为类变量赋值。它们被绑定到特定请求线程,该线程在下一个请求中不再存在。

#1


8  

You need to instruct JSF that you've already taken the response handling in your hands and that JSF should not handle the default navigation when the action method is finished. Add this to the end of the action method:

您需要指示JSF您已经完成了响应处理,并且在操作方法完成时JSF不应该处理默认导航。将其添加到操作方法的末尾:

facesContext.responseComplete();

Update as per the comments, you're accessing ExternalContext as an instance variable which suggests that you assigned it and the FacesContext as class variable, either static or as property of a session scoped bean. This is definitely a bad idea. You should get hand of them inside the local method by FacesContext#getCurrentInstance() and never assign them as class variable. They are namely bound to a specific request thread which do not exist in next request anymore.

根据注释更新,您将访问ExternalContext作为实例变量,它建议您将它和FacesContext分配为类变量,静态或作为会话范围bean的属性。这绝对是一个坏主意。你应该通过FacesContext#getCurrentInstance()在本地方法中获取它们,并且永远不要将它们作为类变量赋值。它们被绑定到特定请求线程,该线程在下一个请求中不再存在。