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

发布于 2024-10-31 22:14:09 字数 3158 浏览 1 评论 0原文

我正在尝试将 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);
    }
}

带有创建 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>

我想要转换的页面:

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

当我尝试时,我只获得一次 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

我做错了什么?

更新了 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();
}

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

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:

<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:

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:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

岁吢 2024-11-07 22:14:09

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

facesContext.responseComplete();

Update 根据注释,您正在将 ExternalContext 作为实例变量访问,这表明您已分配它和 FacesContext 作为类变量,可以是静态,也可以作为会话作用域 bean 的属性。这绝对是一个坏主意。您应该通过 FacesContext#getCurrentInstance() 在本地方法中获取它们,并且从不将它们分配为类变量。即它们绑定到特定的请求线程,该线程在下一个请求中不再存在。

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:

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文