Jasper Reports print unconfigured report in xlsx,

2019-07-29 01:09发布

A web application prints PDF reports without problems, but when reports in xlsx, docx, csv, rtf, etc. are all not configured correctly. The browser tries to save the file always with the .xhtml extension.

How do you export the report to a browser such that the file exports with the correct filename and media type?

Code:

public void gerarJasper(String name, String type, List data, Map params) throws IllegalArgumentException, RuntimeException, Exception {

    boolean found = false;
    for (int i = 0; i < VALID_TYPES.length; i++) {
        if (VALID_TYPES[i].equals(type)) {
            found = true;
            break;
        }
    }
    if (!found) {
        throw new IllegalArgumentException("Tipo solicitado '" + type + "' inválido");
    }

    // Procurar recurso de design de relatório compilado
    ExternalContext econtext = FacesContext.getCurrentInstance().getExternalContext();

    InputStream stream = econtext.getResourceAsStream(PREFIX + name + SUFFIX);
    if (stream == null) {
        throw new IllegalArgumentException("O relatório '" + name + "' não existe");
    }

    FacesContext fc = FacesContext.getCurrentInstance();
    ServletContext context = (ServletContext)fc.getExternalContext().getContext();
    String path = context.getRealPath(File.separator) + "resources/jasper" + File.separator;
    String logo = context.getRealPath(File.separator) + "resources/imagens" + File.separator;
    params.put("SUBREPORT_DIR", path);
    params.put("LOGO_DIR", logo);                

    JRDataSource ds = new JRBeanArrayDataSource(data.toArray());
    JasperPrint jasperPrint = null;
    try {
        jasperPrint = JasperFillManager.fillReport(stream, params, ds);
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new FacesException(e);
    } finally {
        try {
            stream.close();
        } catch (IOException e) {
        }
    }

    JRExporter exporter = null;
    HttpServletResponse response = (HttpServletResponse) econtext.getResponse();
    FacesContext fcontext = FacesContext.getCurrentInstance();
    try {
        response.setContentType(type);
        if ("application/pdf".equals(type)) {
            exporter = new JRPdfExporter();
            exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
            exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, response.getOutputStream());
        } else if ("text/html".equals(type)) {
            exporter = new JRHtmlExporter();
            exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
            exporter.setParameter(JRExporterParameter.OUTPUT_WRITER, response.getWriter());
            // Tornar imagens disponíveis para a saída HTML
            HttpServletRequest request = (HttpServletRequest) fcontext.getExternalContext().getRequest();
            request.getSession().setAttribute(ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE, jasperPrint);
            exporter.setParameter(JRHtmlExporterParameter.IMAGES_MAP, new HashMap());
            // A seguinte instrução requer mapeamento / imagem
            // para o imageServlet no web.xml.
            //
            // Este servlet serve imagens, incluindo imagens px
            // para espaçamento.
            //
            // Sirva as imagens diretamente para não
            // incorrermos em tempo extra associado a
            // a uma solicitação JSF para uma entidade não-JSF.
            exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, request.getContextPath() + "/image?image=");
        }else if("application/xlsx".equals(type)){
            exporter = new JRXlsxExporter();                
            exporter.setParameter(JRXlsExporterParameter.JASPER_PRINT, jasperPrint);                
            exporter.setParameter(JRXlsExporterParameter.OUTPUT_STREAM, response.getOutputStream());
            //exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_COLUMNS,new Boolean(true));                                
            exporter.setParameter(JRXlsExporterParameter.OUTPUT_FILE, name+".xlsx");
            exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.FALSE);
            exporter.setParameter(JRXlsExporterParameter.IS_DETECT_CELL_TYPE, Boolean.TRUE);
            exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE);
            exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);

        }else if("application/csv".equals(type)){
            exporter = new JRCsvExporter();             
            exporter.setParameter(JRCsvExporterParameter.JASPER_PRINT, jasperPrint);                
            exporter.setParameter(JRCsvExporterParameter.OUTPUT_STREAM, response.getOutputStream());
            exporter.setParameter(JRCsvExporterParameter.OUTPUT_FILE_NAME, name+".csv");
        }else if("application/docx".equals(type)){
            exporter = new JRDocxExporter();
            exporter.setParameter(JRDocxExporterParameter.JASPER_PRINT, jasperPrint);
            exporter.setParameter(JRDocxExporterParameter.OUTPUT_STREAM, response.getOutputStream());
        } else if("application/rtf".equals(type)){
            exporter = new JRRtfExporter();
            exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
            exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, response.getOutputStream());
        }
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new FacesException(e);
    }

    try {
        exporter.exportReport();
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new FacesException(e);
    }
    fcontext.responseComplete();
}

1条回答
Rolldiameter
2楼-- · 2019-07-29 01:44

Summary

The "Content-Disposition" HTTP response header is not being set. Set it using:

response.setHeader(
  "Content-Disposition",
  "attachment; filename=".concat(name).concat(filenameExtension)
);

But this is not the only problem.

Servlet vs. JSF Page

Although the question does not include how the report is being called, I'm going to assume it's one of:

<a href="report.jsf">Download</a>
<a href="report.xhtml">Download</a>

This will lead to troubles (such as exceptions due to the output stream being closed twice). Use a Servlet, instead, to generate a report for download. The link will become:

<a href="/servlets/ReportServlet">Download</a>

See also:

Don't use FacesContext to get the HTTP response stream. Use a Servlet, instead, and implement the doGet and doPost methods.

Code Simplifications

The following code:

boolean found = false;
for (int i = 0; i < VALID_TYPES.length; i++) {
    if (VALID_TYPES[i].equals(type)) {
        found = true;
        break;
    }
}
if (!found) {
    throw new IllegalArgumentException("Tipo solicitado '" + type + "' inválido");
}

Reduces to:

if( !Arrays.asList(VALID_TYPES).contains(type) ) {
    throw new IllegalArgumentException("Tipo solicitado '" + type + "' inválido");
}

Create a ReportFormat enumeration that associates a file extension with its application type in a robust, reusable way:

public enum ReportFormat {

    /**
     * Adobe Acrobat Portable Document Format.
     *
     * @see https://tools.ietf.org/html/rfc3778
     */
    PDF("application/pdf", "pdf"),

    /**
     * Hypertext Mark-up Language.
     *
     * @see https://www.ietf.org/rfc/rfc2854.txt
     */
    HTML("text/html", "html"),

    /**
     * Comma-separated Values.
     *
     * @see https://tools.ietf.org/html/rfc4180
     */
    CSV("text/csv", "csv"),

    /**
     * Proprietary Microsoft Excel Format (see also: CSV).
     *
     * @see http://www.iana.org/assignments/media-types/application/vnd.ms-excel
     */
    XLS("application/vnd.ms-excel", "xls"),

    /**
     * The media type as defined by IANA and IETF.
     *
     * @see http://www.iana.org/assignments/media-types/media-types.xhtml
     */
    private final String mediaType;

    /**
     * The filename extension typically used for this format's media type.
     */
    private final String extension;

    private ReportFormat(
            final String mediaType,
            final String extension) {
        this.mediaType = mediaType;
        this.extension = extension;
    }

    public String getFilenameExtension() {
        return this.extension;
    }

    /**
     * Returns the media type (formerly MIME type) for this report format
     * suitable for inclusion in the content-header of an HTTP response.
     *
     * @return The report format media type.
     * @see http://www.iana.org/assignments/media-types/media-types.xhtml
     */
    public String getMediaType() {
        return this.mediaType;
    }
}

Now, instead of passing in the type, you can write:

public void gerarJasper(String name, ReportFormat reportFormat, ... ) {
}

Then there's no need to check for the report format because only known types can be passed. This further reduces the code to:

if( reportFormat == null ) {
    throw new IllegalArgumentException("Tipo solicitado null inválido");
}

Alternatively, assume a default format and the method will throw one few error conditions to handle:

if( reportFormat == null ) {
    // Returns ReportFormat.PDF by default.
    reportFormat = getDefaultFormat();
}

Next, the following code:

} catch (RuntimeException e) {
    throw e;
} catch (Exception e) {
    throw new FacesException(e);
}

Reduces to:

} catch (Exception e) {
    throw new FacesException(e);
}

There are a number of other simplifications that can be made. See the Command Pattern for details. For example:

FacesContext fc = FacesContext.getCurrentInstance();
FacesContext fcontext = FacesContext.getCurrentInstance();

Only one FacesContext instance is necessary, so you can delete fcontext (or fc).

As for the problem, the content disposition is not set via the HTTP response. With ReportFormat in place, create some new methods:

private void setHeader(final String name, final String value) {
    getResponse().setHeader(name, value);
}

private HttpServletResponse getResponse() {
    final ExternalContext ctx = getFacesContext().getExternalContext();
    final HttpServletResponse response = (HttpServletResponse) ctx.getResponse();

    return response;
}

Next, introduce a constant and additional methods:

private static final String CONTENT_DISPOSITION = "Content-Disposition";

protected void setContentType(final String mediaType) {
    getResponse().setContentType(mediaType);
}

protected void setContentDisposition(final String filename) {
    setHeader(CONTENT_DISPOSITION, "attachment; filename=".concat(filename));
}

Call them like:

setContentType( reportFormat.getMediaType() );
setContentDisposition( name + "." + reportFormat.getFilenameExtension() );

The code shown in the question is overly complex. Applying some common design patterns will make it easier to maintain.

查看更多
登录 后发表回答