用一个程序生成另一个程序
如果您具有业务应用程序开发的经验,那么很可能会遇到要求该应用程序具有灵活的报告机制的需求。 我工作的公司主要专注于开发业务解决方案,而报告是必不可少的,实际上,它必须包含我们开发的所有企业系统的方面。 为了在我们的系统中实现灵活的报告,我们开发了自己的开源报告生成器-YARG (又一次获得了另一个报告生成器)(以Apache 2.0许可分发)。 现在,YARG是CUBA平台报告的核心-CUBA 平台本身就是我们开发的所有系统的基础。
为什么需要开发一个新的
首先,让我指出我们不是车轮发明家。 只要这些解决方案适合我们,我们就一直在寻找与之集成的解决方案。 不幸的是,在这种情况下,我们找不到符合我们确定的以下要求的任何开源工具:
- 以模板格式生成报告和/或将输出转换为PDF
- 避免使用外部工具来创建报告模板(Microsoft Office或Libreoffice应该足够了)
- 支持各种格式的模板: DOC,ODT,XLS,DOCX,XLSX,HTML
- 能够将复杂的XLS和XLSX模板与图表,公式等配合使用。
- 能够使用HTML布局和插入/嵌入图像
- 拆分出数据层(报告结构和数据获取)和表示层(报告模板)
- 启用各种数据获取方法,例如SQL,JPQL或Groovy脚本
- 与IoC框架( Spring , Guice )集成的能力
- 将该工具用作独立应用程序的功能,以便能够在Java生态系统之外使用它(例如,使用PHP生成报告)
- 以透明XML格式存储报告结构
我们可以找到的最接近的工具是JasperReports ,但是有一些阻止程序使我们无法使用它:
- 免费版本无法生成DOC报告(有一个提供此功能的商业库)
- XLS报告非常有限,无法使用图表,公式和单元格格式
- 要创建报告,必须具有一定的技能和知识,以及如何使用非常特定的工具(例如iReports )
- 数据层和表示层之间没有明确的分隔
当然,我们研究了许多其他不同的工具,但是我们发现的所有其他库都集中在某种特定格式上。 我们希望有一个万能的报表功能-一种用于所有报表的工具。
考虑到以上列出的所有观点和想法,我们决定开发另一种定制的报告生成工具。
什么是内幕
当我们开始YARG时,找到用于XLS集成的库( POI-HSSF , JXLS等)不是问题。 我们决定选择Apache POI作为最受欢迎和受支持的库。
DOC集成的情况则完全相反。 在开源市场上只有很少的选择( POI-HWPF , COM和UNO Runtime )。 POI-HWPF库在许多方面都非常有限,我们认为它不是合适的选择。 我们必须在COM和UNO运行时之间进行选择,这实际上是用于OpenOffice服务器端集成的API。
因此,经过深入调查,我们决定选择UNO Runtime ,主要是因为成功将其用于以完全不同的语言(例如Python,Ruby,C#等)编码的系统的人们的积极反馈。
尽管POI-HSSF的使用非常简单(图表除外),但我们在集成UNO Runtime时面临许多挑战:
- 没有明确的API可用于表格
- 每次生成报告时,OpenOffice都会启动。 最初,我们使用bootstrapconnector来管理OpenOffice进程,但是后来很明显,在很多情况下,它不会在生成报告后终止该进程。 因此,我们不得不重新实现OpenOffice启动和关闭的逻辑(感谢jodconverter开发人员,他们在此问题上指出了许多好主意)
- 另外, UNO Runtime (和OpenOffice Server本身)在线程安全方面也存在严重问题,如果发生内部错误,这可能导致服务器冻结或终止自身。 为了克服这个问题,我们必须实现一种机制,以便在服务器发生故障时重新启动报告,这显然会对性能造成不利影响
后来,当DOCX4J库变得非常成熟和流行时,我们支持XLSX / DOCX。 DOCX4J库的主要优点是,它提供了对文档结构的必要的低级访问(基本上,您使用XML进行操作)。 使用DOCX4J的另一个好处是,它不需要OpenOffice服务器集成即可生成DOCX报告。
另外,还可以使用带有Freemarker标记的文档作为报告模板。 我们通常使用它生成非常自定义的HTML报表,然后将结果转换为PDF 。
最后,YARG基础结构是以可扩展的方式开发的,因此有经验的用户可以自己实现与任何其他模板类型的集成。
你好世界报告
让我们认识一下YARG。 报告生成器的主要思想是将数据层和表示层分开。 数据层使脚本编制或直接SQL查询能够获取所需的信息,而表示层则代表所获取数据的标记。
YARG的所有报告均由所谓的“乐队”组成 。 带是将数据和表示层链接在一起的东西。 因此,每个乐队都知道从何处获取数据以及将数据放置在模板中的位置。
例如,我们想将所有员工打印到Excel电子表格中。 我们将需要创建“ Staff”乐队并定义一个SQL查询以获取员工列表:
select name, surname, position from staff
Java代码
ReportBuilder reportBuilder = new ReportBuilder();
ReportTemplateBuilder reportTemplateBuilder = new ReportTemplateBuilder().documentPath("/home/haulmont/templates/staff.xls").documentName("staff.xls").outputType(ReportOutputType.xls).readFileFromPath();
reportBuilder.template(reportTemplateBuilder.build());
BandBuilder bandBuilder = new BandBuilder();
ReportBand staff= bandBuilder.name("Staff").query("Staff", "select name, surname, position from staff", "sql").build();
reportBuilder.band(staff);
Report report = reportBuilder.build();Reporting reporting = new Reporting();
reporting.setFormatterFactory(new DefaultFormatterFactory());
reporting.setLoaderFactory(new DefaultLoaderFactory().setSqlDataLoader(new SqlDataLoader(datasource)));ReportOutputDocument reportOutputDocument = reporting.runReport(new RunParams(report), new FileOutputStream("/home/haulmont/reports/staff.xls"));
剩下的唯一事情就是创建XLS模板:
开始了! 只需运行该程序即可享受结果!
没有Java的进阶范例
假设我们有一个书店网络。 需要生成一个XLS报告,其中显示了已售书的列表,并参考了售书的书店。 此外,我们没有Java开发人员,只有拥有XML和SQL基本技能的系统管理员。
首先,我们需要为报告创建XLS模板:
如您所见,我们定义了两个命名区域(对应于乐队):商店(蓝色)和书籍实例(白色)。
现在,我们必须从数据库中获取所需的数据:
select shop.id as "id", shop.name as "name", shop.address as "address"
from store shopselect book.author as "author", book.name as "name", book.price as "price", count(*) as "count"
from book book where book.store_id = ${Shop.id}
group by book.author, book.name, book.price
最后,我们使用XML声明报告的波段结构:
<?xml version="1.0" encoding="UTF-8"?>
<report name="report"><templates><template code="DEFAULT" documentName="bookstore.xls" documentPath="./test/sample/bookstore/bookstore.xls" outputType="xls" outputNamePattern="bookstore.xls"/></templates><rootBand name="Root" orientation="H"><bands><band name="Header" orientation="H"/><band name="Shop" orientation="H"><bands><band name="Book" orientation="H"><queries><query name="Book" type="sql"><script>select book.author as "author", book.name as "name", book.price as "price", count(*) as "count" from book where book.store_id = ${Shop.id} group by book.author, book.name, book.price</script></query></queries></band></bands><queries><query name="Shop" type="sql"><script>select shop.id as "id", shop.name as "name", shop.address as "address" from store shop</script></query></queries></band></bands><queries/></rootBand>
</report>
让我们启动报告并查看结果(下面的“ 独立”部分描述了如何运行报告):
该用例表明,您可以引用父带:book.store_id = $ {Shop.id}。 这使我们能够过滤每个特定书店出售的书籍。
一个更高级的例子
现在,我们创建一个发票报告。 我们将创建DOCX文档,然后将其转换为不可变形式– PDF文档。 为了说明如何加载数据的另一种方式,我们将使用groovy脚本,而不是直接SQL查询:
<?xml version="1.0" encoding="UTF-8"?>
<report name="report"><templates><template code="DEFAULT" documentName="invoice.docx" documentPath="./test/sample/invoice/invoice.docx" outputType="pdf" outputNamePattern="invoice.pdf"/></templates><formats><format name="Main.date" format="dd/MM/yyyy"/><format name="Main.signature" format="${html}"/></formats><rootBand name="Root" orientation="H"><bands><band name="Main" orientation="H"><queries><query name="Main" type="groovy"><script>return [['invoiceNumber':99987,'client' : 'Google Inc.','date' : new Date(),'addLine1': '1600 Amphitheatre Pkwy','addLine2': 'Mountain View, USA','addLine3':'CA 94043','signature':<![CDATA['<html><body><b><font color="red">Mr. Yarg</font></b></body></html>']]>]]</script></query></queries></band><band name="Items" orientation="H"><queries><query name="Main" type="groovy"><script>return [['name':'Java Concurrency in practice', 'price' : 15000],['name':'Clear code', 'price' : 13000],['name':'Scala in action', 'price' : 12000]]</script></query></queries></band></bands><queries/></rootBand>
</report>
您可能已经注意到,Groovy脚本返回List <Map <String,Object >>对象。 因此,每个项目都表示为一个键(参数名称)和值(参数值)。
最后,我们将需要创建一个DOCX模板:
要将底部表格链接到书籍列表,我们使用## band = Items标记。
生成报告后,我们得到以下输出:
IoC框架集成
如前所述,要求之一是提供集成到IoC框架( Spring , Guice )的能力。 我们将YARG用作CUBA平台(我们用于企业应用程序开发的高级Java框架)中强大的报告引擎的一部分。 CUBA采用Spring作为IoC机制,让我们看一下YARG如何集成到平台中:
<bean id="reporting_lib_Scripting" class="com.haulmont.reports.libintegration.ReportingScriptingImpl"/>
<bean id="reporting_lib_GroovyDataLoader" class="com.haulmont.yarg.loaders.impl.GroovyDataLoader"><constructor-arg ref="reporting_lib_Scripting"/>
</bean>
<bean id="reporting_lib_SqlDataLoader" class="com.haulmont.yarg.loaders.impl.SqlDataLoader"><constructor-arg ref="dataSource"/>
</bean>
<bean id="reporting_lib_JpqlDataLoader" class="com.haulmont.reports.libintegration.JpqlDataDataLoader"/>
<bean id="reporting_lib_OfficeIntegration"class="com.haulmont.reports.libintegration.CubaOfficeIntegration"><constructor-arg value="${cuba.reporting.openoffice.path?:/}"/><constructor-arg><list><value>8100</value><value>8101</value><value>8102</value><value>8103</value></list></constructor-arg><property name="displayDeviceAvailable"><value>${cuba.reporting.displayDeviceAvailable?:false}</value></property><property name="timeoutInSeconds"><value>${cuba.reporting.openoffice.docFormatterTimeout?:20}</value></property>
</bean>
<bean id="reporting_lib_FormatterFactory"class="com.haulmont.yarg.formatters.factory.DefaultFormatterFactory"><property name="officeIntegration" ref="reporting_lib_OfficeIntegration"/>
</bean>
<bean id="reporting_lib_LoaderFactory" class="com.haulmont.yarg.loaders.factory.DefaultLoaderFactory"><property name="dataLoaders"><map><entry key="sql" value-ref="reporting_lib_SqlDataLoader"/><entry key="groovy" value-ref="reporting_lib_GroovyDataLoader"/><entry key="jpql" value-ref="reporting_lib_JpqlDataLoader"/></map></property>
</bean>
<bean id="reporting_lib_Reporting" class="com.haulmont.yarg.reporting.Reporting"><property name="formatterFactory" ref="reporting_lib_FormatterFactory"/><property name="loaderFactory" ref="reporting_lib_LoaderFactory"/>
</bean>
为了将YARG集成到Spring Framework中,应注册以下bean:
-
reporting_lib_Reporting
–提供对核心报告生成功能的访问。 report_lib_FormatterFactory –将输出管理为不同的格式(DOCX,XSLX,DOC等) -
reporting_lib_LoaderFactory
–提供数据加载功能(包含与不同源相对应的多个Bean) -
reporting_lib_OfficeIntegration
–将报表生成器与OpenOffice服务器集成(生成DOC和ODT报表所需)
如您所见,YARG可以轻松地嵌入到您的应用程序中。
独立使用
YARG的另一个重要功能是可以用作独立应用程序。 从技术上讲,如果已安装JRE,则可以从命令提示符下运行报告生成器。 例如,如果您有服务器端PHP应用程序,并且想在应用程序中启用报告,则只需创建一个XLS模板,以XML声明报告结构,然后从命令提示符下启动YARG:
yarg -rp ~/report.xml -op ~/result.xls “-Pparam1=20/04/2014”
CUBA平台提供的更多功能
YARG已深度集成到CUBA平台中,并充当该平台中实现的强大报告机制的核心引擎。
首先,您可以使用CUBA Studio一键式嵌入报告( 此处提供只读演示版):
CUBA为报表管理提供了方便的用户界面:
- 报告浏览器,带有导入/导出和运行报告的选项。
- 报告编辑器允许您创建任何复杂性的报告(定义带,输入参数,管理模板,使用Groovy,SQL和JPQL选择数据):
- CUBA引入了报告向导功能。 借助向导,即使对编程的了解有限,任何用户都可以快速创建报告:
总结这篇文章,让我跳过通常无聊的深层思考(特别是因为可以在此处找到所有信息),并提出了一些我最喜欢的报告:
因此,如果您有兴趣,请点击链接并了解更多信息! 请注意,YARG是完全免费的,可以在GitHub上使用 。
翻译自: https://www.javacodegeeks.com/2015/09/yet-another-report-generator.html
用一个程序生成另一个程序