【问题标题】:Jasper Reports print unconfigured report in xlsx, docx, csv, rtf, etcJasper Reports 以 xlsx、docx、csv、rtf 等格式打印未配置的报告
【发布时间】:2018-01-17 20:17:24
【问题描述】:

Web 应用程序可以毫无问题地打印 PDF 报告,但是当 xlsx、docx、csv、rtf 等格式的报告均未正确配置时。浏览器会尝试始终使用 .xhtml 扩展名保存文件。

如何将报告导出到浏览器,以便文件以正确的文件名和媒体类型导出?

代码:

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

【问题讨论】:

    标签: java jasper-reports


    【解决方案1】:

    总结

    "Content-Disposition" HTTP 响应标头未设置。设置它使用:

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

    但这不是唯一的问题。

    Servlet 与 JSF 页面

    虽然问题不包括报告的调用方式,但我会假设它是以下之一:

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

    这会导致麻烦(例如由于输出流被关闭两次而导致的异常)。相反,使用 Servlet 生成报告以供下载。链接将变为:

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

    另见:

    不要使用FacesContext 来获取HTTP 响应流。改为使用 Servlet,并实现 doGetdoPost 方法。

    代码简化

    以下代码:

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

    减少到:

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

    创建一个ReportFormat 枚举,以一种稳健、可重用的方式将文件扩展名与其应用程序类型相关联:

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

    现在,您可以这样写,而不是传递type

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

    那么就不需要检查报告格式,因为只能传递已知类型。这进一步将代码简化为:

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

    或者,假设一种默认格式,该方法将抛出一些错误条件来处理:

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

    接下来,如下代码:

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

    减少到:

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

    还可以进行许多其他简化。有关详细信息,请参阅Command Pattern。例如:

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

    只需要一个FacesContext 实例,因此您可以删除fcontext(或fc)。

    至于问题,内容配置不是通过 HTTP 响应设置的。使用ReportFormat,创建一些新方法:

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

    接下来,介绍一个常量和附加方法:

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

    这样称呼他们:

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

    问题中显示的代码过于复杂。应用一些常见的设计模式会更容易维护。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多