Apache POI OPCPackage 无法保存 Jasper 报告生成的 XLSX

Apache POI OPCPackage unable to save Jasper Report generated XLSX

总体行动计划是使用 JasperReports API 生成 XLSX 文件,然后将其输入 Apache POI 的加密代码。此处找到示例:http://www.quicklyjava.com/create-password-protected-excel-using-apache-poi/.

不幸的是,当文件从 Jasper Report 生成时,我无法对其进行加密,但是如果我在 MS Excel 中创建一个文件并通过代码加载它,那么它就可以加密了。因此 Apache POI 库可以很好地处理来自 Excel.

的文件

经过调试,我相信我能够查明问题所在。

我们使用 OPCPackage class 打开文件(或者在另一种情况下加载输入流),class 有一个设置所有字段的方法 getParts() 。特别是 packageProperties。似乎 packageProperties 正在加载特定的 Package Part 'application/vnd.openxmlformats-package.core-properties+xml'。但是,这部分在 Jasper Report 生成的 XLSX 文件中找不到,因此保持为空。

// Check OPC compliance rule M4.1
if (part.getContentType().equals(ContentTypes.CORE_PROPERTIES_PART)) {
    if (!hasCorePropertiesPart) {hasCorePropertiesPart = true;

如果通过,下面几行我们分配 this.packageProperties:

// Core properties case-- use first CoreProperties part we come across
// and ignore any subsequent ones
if (unmarshallPart instanceof PackagePropertiesPart && needCorePropertiesPart) {
    this.packageProperties = (PackagePropertiesPart) unmarshallPart;

文件打开正常,但是当我尝试保存文件时,saveImpl 方法调用了一个名为 的方法addPackagePart 传入 'packageProperties' 变量。 class 然后抛出异常,因为该字段为空。来自 ZipPackage#saveImpl

// If the core properties part does not exist in the part list,
// we save it as well
if (this.getPartsByRelationshipType(PackageRelationshipTypes.CORE_PROPERTIES).size() == 0 && this.getPartsByRelationshipType(PackageRelationshipTypes.CORE_PROPERTIES_ECMA376).size() == 0    ) {
    logger.log(POILogger.DEBUG,"Save core properties part");
        // Add core properties to part list ...
        addPackagePart(this.packageProperties);

例外情况:

org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException: Fail to save: an error occurs while saving the package : part
    at org.apache.poi.openxml4j.opc.ZipPackage.saveImpl(ZipPackage.java:503)
    at org.apache.poi.openxml4j.opc.OPCPackage.save(OPCPackage.java:1425)
    at com.krfs.web.controller.report.BaseReportController.encryptXlsxOutputStream(BaseReportController.java:717)
    at com.krfs.web.controller.report.BaseReportController.generateJasperReportOutput(BaseReportController.java:547)
    at com.krfs.web.controller.report.BaseReportController.processReportRunningAndGeneration(BaseReportController.java:382)
    at com.krfs.web.controller.report.StandardReportController.processReportRunningAndGeneration(StandardReportController.java:25)
    at com.krfs.web.controller.report.StandardReportController.run(StandardReportController.java:82)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:781)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:721)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.doFilterInternal(OpenEntityManagerInViewFilter.java:177)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at com.krfs.web.filter.RedirectFilter.doFilter(RedirectFilter.java:62)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at com.krfs.web.filter.XssFilter.doFilter(XssFilter.java:17)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.authentication.switchuser.SwitchUserFilter.doFilter(SwitchUserFilter.java:181)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at com.krfs.web.filter.CustomLoginFilter.doFilter(CustomLoginFilter.java:156)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at com.krfs.web.filter.CsrfFilter.doFilter(CsrfFilter.java:52)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at fr.xebia.servlet.filter.ExpiresFilter.doFilter(ExpiresFilter.java:1243)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at com.krfs.web.filter.SecurityHeadersFilter.doFilter(SecurityHeadersFilter.java:22)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at com.krfs.web.filter.LoggingFilter.doFilter(LoggingFilter.java:44)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:108)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:744)
Caused by: java.lang.IllegalArgumentException: part
    at org.apache.poi.openxml4j.opc.OPCPackage.addPackagePart(OPCPackage.java:873)
    at org.apache.poi.openxml4j.opc.ZipPackage.saveImpl(ZipPackage.java:448)
    ... 104 more

Jasper Reports 代码是导出 XLSX 文件的标准代码。

JRXlsAbstractExporter exporter = new JRXlsxExporter();

JasperPrint jasperPrint = fillJasperPrint(reportMessenger, reportFile, reportParameters);

exporter.setParameter(JRXlsExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRXlsExporterParameter.OUTPUT_STREAM, outputStream);
exporter.setParameter(JRXlsExporterParameter.IS_DETECT_CELL_TYPE, Boolean.TRUE);                    
exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE);

exporter.exportReport();

这很好地创建了 XLSX,但是当我提取内容并查看包时,我没有看到属性部分 application/vnd.openxmlformats- package.core-properties+xml 这显然是 Apache POI 保存文件所必需的。

加密代码:

POIFSFileSystem fs = new POIFSFileSystem();
        EncryptionInfo info = new EncryptionInfo(fs, EncryptionMode.agile);
        Encryptor enc = info.getEncryptor();
        enc.confirmPassword(reportPassword);

        try (OPCPackage opc = OPCPackage.open(new ByteArrayInputStream(xlsxOutputStream.toByteArray()))) {
            OutputStream os = enc.getDataStream(fs);
            opc.save(os);
        }

        fs.writeFilesystem(encryptedOutputStream);

依赖版本:

jasper 报告 - 4.7.1(我也尝试了 6.0.2,结果相同)

apache poi - 3.11

所以我的问题是,如何让这两个库协调工作,以便我可以加密来自 jasper 报告的文件?有没有办法在导出文件之前注入输出类型部分core-properties+xml?有没有办法绕过 Apache POI 库来跳过查找该部分?

我希望有人看到类似的东西可以帮助我。

干杯,谢谢!

更新:Whosebug 不允许上传文件,因此您可以在 github 中找到整个项目:https://github.com/nestoru/xlsxenc

我可以确认这是 apache POI 库的错误,而不是 jasper 报告的错误。我在这里附上了相关文件和临时解决方法,其中涉及使用 libreoffice(迄今为止最好的开源 excel 处理套件、实用程序甚至可能是库)。解决方法包括将 libreoffice 作为 shell 进程调用到 "correct" 从 jasper 报告中以 POI "understands" 的方式出现的 xlsx。

以下是如何根据提供的文件从头开始或部分重新创建所有内容:

  1. 从 ireport 加载附加的 jrxml 文件,它将只输出我的徽标。
  2. Select 来自 ireport 的 xlsx 输出类型,您将同时获得 report1.xlsx。
  3. 现在尝试使用附带的java程序加密文件,您将得到如下所示的错误:

    $ javac -cp "" Enc.java $ java -cp ".:" Enc /home/dev/xlsxenc/report1.xlsx /tmp/out.xlsx

    线程异常"main" org.apache.poi.openxml4j.exceptions.OpenXML4J运行timeException: Fail to save: 保存包时出错: 在 org.apache.poi.openxml4j.opc.ZipPackage.saveImpl(ZipPackage.java:503) 在 org.apache.poi.openxml4j.opc.OPCPackage.save(OPCPackage.java:1425) 在 Enc.main (Enc.java:34) 原因:java.lang.IllegalArgumentException:部分位于 org.apache.poi.openxml4j.opc.OPCPackage.addPackagePart(OPCPackage.java:873) org.apache.poi.openxml4j.opc.ZipPackage.saveImpl(ZipPackage.java:448) ... 还有 2 个

  4. 现在是解决方法的时候了。 运行 下面的命令到 "correct" xlsx:

    $ libreoffice --headless --convert-to xlsx /home/dev/xlsxenc/report1.xlsx --outdir /tmp

    使用 Calc Office Open 转换 /home/dev/xlsxenc/report1.xlsx -> /tmp/report1.xlsx XML

  5. 并确认程序现在加密文件。当你打开它时,它会要求输入密码。只需输入 "password" 即可打开。

    $ java -cp ".:*" Enc /tmp/report1.xlsx /tmp/out.xlsx

    $ libreoffice /tmp/out.xlsx

    Xlib:显示“:10.0”时缺少扩展 "XINERAMA"。

据我所知,Jasper Reports 正在编写稍微不符合规范的文件。 Excel 不能与他们一起工作,但仍然不合规。

Apache POI 可以很好地读取这些文件,如 unit tests like this one 所示。 Apache POI 也可以保存它们,如果通过类似 XSSFWorkbook 或类似的东西打开和保存,这会触发缺失部分的创建。

不幸的是,对于您的用例,您在 OPCPacakge 级别工作,那里有一个 POI 错误。这已在 r1662971 中修复,并将在 POI 3.12 beta 2 及更高版本中。

现在,您可以通过更改以下行来触发核心属性创建:

OPCPackage opc = OPCPackage.open(inputFilePath, PackageAccess.READ_WRITE);
OutputStream os = enc.getDataStream(fs);
opc.save(os);

改为:

OPCPackage opc = OPCPackage.open(inputFilePath, PackageAccess.READ_WRITE);
OutputStream os = enc.getDataStream(fs);
opc.getProperties();
opc.save(os);

使用 POI 3.12 beta 2 或更高版本后,您将不需要额外调用 getProperties()

此外,您链接到的 github 中的代码中有一些已弃用的调用,您可能需要查看 TestEncryptor.encryptPackageWithoutCoreProperties() 以了解如何稍微调整它以在没有弃用方法的情况下工作。