JasperReport with OutputStream not exporting to PD

2019-09-14 19:59发布

I am using JasperReport to export a report to a PDF. The code runs fine with no exception messages showing up in the console/log. However, the report does not export to the browser. In other words, the report is being created, I just cannot download or gain access to it.

Here is the export code:

public void generatePDFReport(Map<String, Object> parameters, JRDataSource jrDataSource, String resource, String filename)
{
    OutputStream os = null;
    try{
        FacesContext context = FacesContext.getCurrentInstance();
        HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
        os = response.getOutputStream();

        InputStream reportTemplate = this.getClass().getClassLoader().getResourceAsStream(resource);
        byte[] pdf = null;

        try {
            JasperDesign masterDesign = JRXmlLoader.load(reportTemplate);
            masterReport = JasperCompileManager.compileReport(masterDesign);
            masterReport.setWhenNoDataType(WhenNoDataTypeEnum.ALL_SECTIONS_NO_DETAIL);
            JasperPrint masterPrint = JasperFillManager.fillReport(masterReport, parameters, jrDataSource);
            pdf = JasperExportManager.exportReportToPdf(masterPrint);
        } catch (JRException e) {
            log.error(e);
        }
        response.setContentType("application/pdf");
        response.setContentLength(pdf.length);
        response.setHeader("Content-disposition", "attachment; filename=\""+filename+"\"");

        context.responseComplete();

        os.write(pdf);

        pdf = null;
    }catch(Exception e){
        log.error(e);
    }finally{
        try{
            os.flush();
            os.close();
        }catch(IOException e){
            log.error(e);
        }
    }
}

I am almost 100% certain that there is nothing wrong with the code as it works fine for different reports (I run the same export code for several other reports and it works as expected for all of them except for this one).

Knowing this, I figured it must have something to do with the report itself. The report is a jrxml JasperReport file. The report was created using iReport. However, I modified the above code to simply save it to the downloads folder and the report is being created perfectly fine.

So, the problem is that the report is successfully being created in the backend but it is not being sent to the front-end (browser) as expected.

I am open to any suggestions as to why this report would not be working.

2条回答
Ridiculous、
2楼-- · 2019-09-14 20:44

I figured out a solution to my problem. Ultimately, I found that there was nothing wrong with the report generation code or the reports, but there was an ajax issue that was preventing the outputstream from exporting the report to the browser.

查看更多
地球回转人心会变
3楼-- · 2019-09-14 20:59

Running the code inside a bean is problematic because:

  • only one call to getOutputStream is allowed per HTTP request
  • the web framework (J2EE/JSF) has likely already written HTTP headers
  • the JSF page has likely already been written as HTML inside a temporary buffer (flushed upon calling responseComplete()).
  • the headers could be reset, but that won't help with the getOutputStream issue
  • calling responseComplete() flushes any HTML along with PDF content to the browser

Use a servlet. The send method of the servlet needn't be any more complex than:

protected void send(final byte[] content) throws IOException {
    setContentLength(content.length);

    try (final OutputStream out = getOutputStream()) {
        out.write(content);
    }
}

Also consider setting the cache so that stale reports are not possible:

protected void disableCache() {
    // https://tools.ietf.org/html/rfc7234#section-7.1.3
    setHeader(CACHE_CONTROL, "private, no-store, no-cache, must-revalidate");

    // https://tools.ietf.org/html/rfc7234#section-5.3
    setHeader(EXPIRES, "Thu, 01 Dec 1994 16:00:00 GMT");

    // https://tools.ietf.org/html/rfc7234#section-5.4
    setHeader(PRAGMA, "no-cache");

    // https://tools.ietf.org/html/rfc7232#section-2.2
    setHeader(LAST_MODIFIED, getServerTimestamp());
}

private String getServerTimestamp() {
    final SimpleDateFormat rfc1123 = new SimpleDateFormat(DATE_RFC_1123, getDefault());

    rfc1123.setTimeZone(getTimeZone("GMT"));

    final Calendar calendar = Calendar.getInstance();
    return rfc1123.format(calendar.getTime());
}

This implies, for example:

@WebServlet(
        name = "ReportServlet",
        urlPatterns = {PATH_SERVLET + "ReportServlet"}
)
public class ReportServlet extends AbstractServlet {
}

And then use a regular anchor link:

<h:outputLink value="/app/path/servlet/Reportservlet">Run Report</h:outputLink>

In summary, don't send binary report data by intercepting a request to a JSF page; use a servlet, instead.

Communications between servlets and JSF pages can be made via:

  • Session variables (HTTPSession on the servlet side)
  • URL parameters

Servlets have the advantage that the JSF overhead is completely avoided, which will make the report run faster from the user's perspective. Also, don't compile the report -- use the .jasper file directly, which will also have performance improvements. (I did not mean to imply using the .jrxml file was the problem, merely that it isn't a necessary step.)

查看更多
登录 后发表回答