依赖关系,一个实例IReportEngine
,目录是在 Spring Boot Web 应用程序中设置 BIRT 中最具挑战性的部分。
这个例子有一些错误处理并且依赖于环境变量用于指定重要目录的路径。
此设置的一个好处是它是一个用于构建和生成报告的独立 ReST API。它的一个附带好处是它根本不依赖于 JSP。
我在 Docker 容器中运行它。
最初计划使用某些界面来尝试其他报告框架,例如 Jasper 报告或其他框架。
这并不完全是您所要求的,但我认为在某些情况下它更好。您可以非常灵活地使用、配置和部署 BIRT 报表运行程序作为这样的应用程序。这不使用 Actian 提供的预打包 BIRT Viewer。
我会为报告制作一个数据容器,称为birt 报告运行程序数据然后将用于日志记录和报告的所有目录放在该容器上,然后您可以将其挂载到 BIRT 容器上。
主要成分
主文件(BirtReportRunnerApplication.java)
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BirtReportRunnerApplication {
public static void main(String[] args) {
SpringApplication.run(BirtReportRunnerApplication.class, args);
}
}
接收报告请求的控制器(ReportController.java)
package com.example.core.web.controller;
import com.example.core.model.BIRTReport;
import com.example.core.service.ReportRunner;
import com.example.core.web.dto.ReportRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController("ReportController")
@RequestMapping("/reports")
public class ReportController {
private Logger logger = LoggerFactory.getLogger(ReportController.class);
@Autowired
@Qualifier("birt")
ReportRunner reportRunner;
@RequestMapping(value = "/birt", method = RequestMethod.POST)
public ResponseEntity<byte[]> getBIRTReport(@RequestBody ReportRequest reportRequest) {
byte[] reportBytes;
ResponseEntity<byte[]> responseEntity;
try {
logger.info("REPORT REQUEST NAME: " + reportRequest.getReportName());
reportBytes =
new BIRTReport(
reportRequest.getReportName(),
reportRequest.getReportParameters(),
reportRunner)
.runReport().getReportContent().toByteArray();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.parseMediaType("application/pdf"));
String fileName = reportRequest.getReportName() + ".pdf";
httpHeaders.setContentDispositionFormData(fileName, fileName);
httpHeaders.setCacheControl("must-revalidate, post-check=0, pre-check=0");
responseEntity = new ResponseEntity<byte[]>(reportBytes, httpHeaders, HttpStatus.OK);
} catch (Exception e) {
responseEntity = new ResponseEntity<byte[]>(HttpStatus.NOT_IMPLEMENTED);
return responseEntity;
}
return responseEntity;
}
}
用于指定要运行的报告的报告请求 DTO (ReportRequest.java)
package com.example.core.web.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class ReportRequest {
private String reportName;
private String reportParameters;
public ReportRequest(@JsonProperty("reportName") String reportName,
@JsonProperty("reportParameters") String reportParameters) {
this.reportName = reportName;
this.reportParameters = reportParameters;
}
public String getReportName() {
return reportName;
}
public String getReportParameters() {
return reportParameters;
}
public void setReportParameters(String reportParameters) {
this.reportParameters = reportParameters;
}
}
报表运行器的接口 (ReportRunner.java)
package com.example.core.service;
import com.example.core.model.Report;
import java.io.ByteArrayOutputStream;
public interface ReportRunner {
ByteArrayOutputStream runReport(Report report);
}
完成大部分工作的最大类 (BIRTReportRunner.java)
package com.example.core.service;
import com.example.core.model.Report;
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.report.engine.api.*;
import org.eclipse.core.internal.registry.RegistryProviderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
@Service
@Qualifier("birt")
public class BIRTReportRunner implements ReportRunner {
private static final String DEFAULT_LOGGING_DIRECTORY = "defaultBirtLoggingDirectory/";
private Logger logger = LoggerFactory.getLogger(BIRTReportRunner.class);
private static String reportOutputDirectory;
private IReportEngine birtReportEngine = null;
@Autowired
private Environment env;
/**
* Starts up and configures the BIRT Report Engine
*/
@PostConstruct
public void startUp() {
if(env.getProperty("birt_report_input_dir") == null)
throw new RuntimeException("Cannot start application since birt report input directory was not specified.");
try {
String birtLoggingDirectory = env.getProperty("birt_logging_directory") == null ? DEFAULT_LOGGING_DIRECTORY : env.getProperty("birt_logging_directory");
Level birtLoggingLevel = env.getProperty("birt_logging_level") == null ? Level.SEVERE : Level.parse(env.getProperty("birt_logging_level"));
EngineConfig engineConfig = new EngineConfig();
logger.info("BIRT LOG DIRECTORY SET TO : {}", birtLoggingDirectory);
logger.info("BIRT LOGGING LEVEL SET TO {}", birtLoggingLevel);
engineConfig.setLogConfig(birtLoggingDirectory, birtLoggingLevel);
// Required due to a bug in BIRT that occurs in calling Startup after the Platform has already been started up
RegistryProviderFactory.releaseDefault();
Platform.startup(engineConfig);
IReportEngineFactory reportEngineFactory = (IReportEngineFactory) Platform.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
birtReportEngine = reportEngineFactory.createReportEngine(engineConfig);
} catch (BirtException e) {
// TODO add logging aspect and find out how to log a platform startup problem from this catch block, if possible, using the aspect.
// Possibly rethrow the exception here and catch it in the aspect.
logger.error("Birt Startup Error: {}", e.getMessage());
}
reportOutputDirectory = env.getProperty("birt_temp_file_output_dir");
}
/**
* Shuts down the BIRT Report Engine
*/
@PreDestroy
public void shutdown() {
birtReportEngine.destroy();
RegistryProviderFactory.releaseDefault();
Platform.shutdown();
}
public File getReportFromFilesystem(String reportName) throws RuntimeException {
String reportDirectory = env.getProperty("birt_report_input_dir");
Path birtReport = Paths.get(reportDirectory + File.separator + reportName + ".rptdesign");
if(!Files.isReadable(birtReport))
throw new RuntimeException("Report " + reportName + " either did not exist or was not writable.");
return birtReport.toFile();
}
/**
* This method creates and executes the report task, the main responsibility
* of the entire Report Service.
* This method is key to enabling pagination for the BIRT report. The IRunTask run task
* is created and then used to generate an ".rptdocument" binary file.
* This binary file is then read by the separately created IRenderTask render
* task. The render task renders the binary document as a binary PDF output
* stream which is then returned from the method.
* <p>
*
* @param birtReport the report object created at the controller to hold the data of the report request.
* @return Returns a ByteArrayOutputStream of the PDF bytes generated by the
*/
@Override
public ByteArrayOutputStream runReport(Report birtReport) {
ByteArrayOutputStream byteArrayOutputStream;
File rptDesignFile;
// get the path to the report design file
try {
rptDesignFile = getReportFromFilesystem(birtReport.getName());
} catch (Exception e) {
logger.error("Error while loading rptdesign: {}.", e.getMessage());
throw new RuntimeException("Could not find report");
}
// process any additional parameters
Map<String, String> parsedParameters = parseParametersAsMap(birtReport.getParameters());
byteArrayOutputStream = new ByteArrayOutputStream();
try {
IReportRunnable reportDesign = birtReportEngine.openReportDesign(rptDesignFile.getPath());
IRunTask runTask = birtReportEngine.createRunTask(reportDesign);
if (parsedParameters.size() > 0) {
for (Map.Entry<String, String> entry : parsedParameters.entrySet()) {
runTask.setParameterValue(entry.getKey(), entry.getValue());
}
}
runTask.validateParameters();
String rptdocument = reportOutputDirectory + File.separator
+ "generated" + File.separator
+ birtReport.getName() + ".rptdocument";
runTask.run(rptdocument);
IReportDocument reportDocument = birtReportEngine.openReportDocument(rptdocument);
IRenderTask renderTask = birtReportEngine.createRenderTask(reportDocument);
PDFRenderOption pdfRenderOption = new PDFRenderOption();
pdfRenderOption.setOption(IPDFRenderOption.REPAGINATE_FOR_PDF, new Boolean(true));
pdfRenderOption.setOutputFormat("pdf");
pdfRenderOption.setOutputStream(byteArrayOutputStream);
renderTask.setRenderOption(pdfRenderOption);
renderTask.render();
renderTask.close();
} catch (EngineException e) {
logger.error("Error while running report task: {}.", e.getMessage());
// TODO add custom message to thrown exception
throw new RuntimeException();
}
return byteArrayOutputStream;
}
/**
* Takes a String of parameters started by '?', delimited by '&', and with
* keys and values split by '=' and returnes a Map of the keys and values
* in the String.
*
* @param reportParameters a String from a HTTP request URL
* @return a map of parameters with Key,Value entries as strings
*/
public Map<String, String> parseParametersAsMap(String reportParameters) {
Map<String, String> parsedParameters = new HashMap<String, String>();
String[] paramArray;
if (reportParameters.isEmpty()) {
throw new IllegalArgumentException("Report parameters cannot be empty");
} else if (!reportParameters.startsWith("?") && !reportParameters.contains("?")) {
throw new IllegalArgumentException("Report parameters must start with a question mark '?'!");
} else {
String noQuestionMark = reportParameters.substring(1, reportParameters.length());
paramArray = noQuestionMark.split("&");
for (String param : paramArray) {
String[] paramGroup = param.split("=");
if (paramGroup.length == 2) {
parsedParameters.put(paramGroup[0], paramGroup[1]);
} else {
parsedParameters.put(paramGroup[0], "");
}
}
}
return parsedParameters;
}
}
报表对象类(Report.java)
package com.example.core.model;
import com.example.core.service.ReportRunner;
import java.io.ByteArrayOutputStream;
import java.util.List;
/**
* A Report object has a byte representation of the report output that can be
* used to write to any output stream. This class is designed around the concept
* of using ByteArrayOutputStreams to write PDFs to an output stream.
*
*
*/
public abstract class Report {
protected String name;
protected String parameters;
protected ByteArrayOutputStream reportContent;
protected ReportRunner reportRunner;
public Report(String name, String parameters, ReportRunner reportRunner) {
this.name = name;
this.parameters = parameters;
this.reportRunner = reportRunner;
}
/**
* This is the processing method for a Report. Once the report is ran it
* populates an internal field with a ByteArrayOutputStream of the
* report content generated during the run process.
* @return Returns itself with the report content output stream created.
*/
public abstract Report runReport();
public ByteArrayOutputStream getReportContent() {
return this.reportContent;
}
public String getName() {
return name;
}
public String getParameters() {
return parameters;
}
}
BIRTReport 对象类 (BIRTReport.java)
package com.example.core.model;
import com.example.core.service.ReportRunner;
public class BIRTReport extends Report {
public BIRTReport(String name, String reportParameters, ReportRunner reportRunner) {
super(name, reportParameters, reportRunner);
}
@Override
public Report runReport() {
this.reportContent = reportRunner.runReport(this);
return this;
}
}
构建文件(build.gradle)
buildscript {
ext {
springBootVersion = '1.3.2.RELEASE'
}
repositories {
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("io.spring.gradle:dependency-management-plugin:0.5.2.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'com.example.simple-birt-runner'
version = '1.0.0'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
ext {
springBootVersion = '1.3.0.M5'
}
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
// BIRT Runtime and ReportEngine dependencies
// These were pulled from the Actuate download site at http://download.eclipse.org/birt/downloads/ for version 4.5.0
compile fileTree(dir: 'lib', include: '*.jar')
/*compile("org.eclipse.birt.runtime:org.eclipse.birt.runtime:4.4.2") {
exclude group: "org.eclipse.birt.runtime", module: "org.apache.poi"
exclude group: "org.eclipse.birt.runtime", module: "org.eclipse.orbit.mongodb"
}*/
compile("org.springframework.boot:spring-boot-starter-tomcat")
// include any runtime JDBC driver dependencies here
testCompile("org.springframework.boot:spring-boot-starter-test")
}
依赖关系
获取 Birt Viewer lib 目录中的所有依赖项并将它们放在类路径中。以下是外部依赖项的完整列表:
com.ibm.icu_54.1.1.v201501272100.jar
com.lowagie.text_2.1.7.v201004222200.jar
derby.jar
flute.jar
javax.wsdl_1.5.1.v201012040544.jar
javax.xml.stream_1.0.1.v201004272200.jar
javax.xml_1.3.4.v201005080400.jar
net.sourceforge.lpg.lpgjavaruntime_1.1.0.v201004271650.jar
org.apache.batik.bridge_1.6.0.v201011041432.jar
org.apache.batik.css_1.6.0.v201011041432.jar
org.apache.batik.dom.svg_1.6.0.v201011041432.jar
org.apache.batik.dom_1.6.1.v201505192100.jar
org.apache.batik.ext.awt_1.6.0.v201011041432.jar
org.apache.batik.parser_1.6.0.v201011041432.jar
org.apache.batik.pdf_1.6.0.v201105071520.jar
org.apache.batik.svggen_1.6.0.v201011041432.jar
org.apache.batik.transcoder_1.6.0.v201011041432.jar
org.apache.batik.util.gui_1.6.0.v201011041432.jar
org.apache.batik.util_1.6.0.v201011041432.jar
org.apache.batik.xml_1.6.0.v201011041432.jar
org.apache.commons.codec_1.6.0.v201305230611.jar
org.apache.commons.logging_1.1.1.v201101211721.jar
org.apache.lucene.core_3.5.0.v20120725-1805.jar
org.apache.poi_3.9.0.v201405241750.jar
org.apache.xerces_2.9.0.v201101211617.jar
org.apache.xml.resolver_1.2.0.v201005080400.jar
org.apache.xml.serializer_2.7.1.v201005080400.jar
org.eclipse.birt.runtime_4.5.0.jar
org.eclipse.core.contenttype_3.5.0.v20150421-2214.jar
org.eclipse.core.expressions_3.5.0.v20150421-2214.jar
org.eclipse.core.filesystem_1.5.0.v20150421-0713.jar
org.eclipse.core.jobs_3.7.0.v20150330-2103.jar
org.eclipse.core.resources_3.10.0.v20150423-0755.jar
org.eclipse.core.runtime.compatibility_3.2.300.v20150423-0821.jar
org.eclipse.core.runtime_3.11.0.v20150405-1723.jar
org.eclipse.datatools.connectivity.apache.derby.dbdefinition_1.0.2.v201107221459.jar
org.eclipse.datatools.connectivity.apache.derby_1.0.103.v201212070447.jar
org.eclipse.datatools.connectivity.console.profile_1.0.10.v201109250955.jar
org.eclipse.datatools.connectivity.db.generic_1.0.1.v201107221459.jar
org.eclipse.datatools.connectivity.dbdefinition.genericJDBC_1.0.2.v201310181001.jar
org.eclipse.datatools.connectivity.oda.consumer_3.2.6.v201403131814.jar
org.eclipse.datatools.connectivity.oda.design_3.3.6.v201403131814.jar
org.eclipse.datatools.connectivity.oda.flatfile_3.1.8.v201403010906.jar
org.eclipse.datatools.connectivity.oda.profile_3.2.9.v201403131814.jar
org.eclipse.datatools.connectivity.oda_3.4.3.v201405301249.jar
org.eclipse.datatools.connectivity.sqm.core_1.2.8.v201401230755.jar
org.eclipse.datatools.connectivity_1.2.11.v201401230755.jar
org.eclipse.datatools.enablement.hsqldb.dbdefinition_1.0.0.v201107221502.jar
org.eclipse.datatools.enablement.hsqldb_1.0.0.v201107221502.jar
org.eclipse.datatools.enablement.ibm.db2.iseries.dbdefinition_1.0.3.v201107221502.jar
org.eclipse.datatools.enablement.ibm.db2.iseries_1.0.2.v201107221502.jar
org.eclipse.datatools.enablement.ibm.db2.luw.dbdefinition_1.0.7.v201405302027.jar
org.eclipse.datatools.enablement.ibm.db2.luw_1.0.3.v201401170830.jar
org.eclipse.datatools.enablement.ibm.db2.zseries.dbdefinition_1.0.4.v201107221502.jar
org.eclipse.datatools.enablement.ibm.db2.zseries_1.0.2.v201107221502.jar
org.eclipse.datatools.enablement.ibm.db2_1.0.0.v201401170830.jar
org.eclipse.datatools.enablement.ibm.informix.dbdefinition_1.0.4.v201107221502.jar
org.eclipse.datatools.enablement.ibm.informix_1.0.1.v201107221502.jar
org.eclipse.datatools.enablement.ibm_1.0.0.v201401170830.jar
org.eclipse.datatools.enablement.msft.sqlserver.dbdefinition_1.0.1.v201201240505.jar
org.eclipse.datatools.enablement.msft.sqlserver_1.0.3.v201308161009.jar
org.eclipse.datatools.enablement.mysql.dbdefinition_1.0.4.v201109022331.jar
org.eclipse.datatools.enablement.mysql_1.0.4.v201212120617.jar
org.eclipse.datatools.enablement.oda.ws_1.2.6.v201403131825.jar
org.eclipse.datatools.enablement.oda.xml_1.2.5.v201403131825.jar
org.eclipse.datatools.enablement.oracle.dbdefinition_1.0.103.v201206010214.jar
org.eclipse.datatools.enablement.oracle_1.0.0.v201107221506.jar
org.eclipse.datatools.enablement.postgresql.dbdefinition_1.0.2.v201110070445.jar
org.eclipse.datatools.enablement.postgresql_1.1.1.v201205252207.jar
org.eclipse.datatools.enablement.sap.maxdb.dbdefinition_1.0.0.v201107221507.jar
org.eclipse.datatools.enablement.sap.maxdb_1.0.0.v201107221507.jar
org.eclipse.datatools.modelbase.dbdefinition_1.0.2.v201107221519.jar
org.eclipse.datatools.modelbase.derby_1.0.0.v201107221519.jar
org.eclipse.datatools.modelbase.sql.query_1.1.4.v201212120619.jar
org.eclipse.datatools.modelbase.sql_1.0.6.v201208230744.jar
org.eclipse.datatools.sqltools.data.core_1.2.3.v201212120623.jar
org.eclipse.datatools.sqltools.parsers.sql.lexer_1.0.1.v201107221520.jar
org.eclipse.datatools.sqltools.parsers.sql.query_1.2.1.v201201250511.jar
org.eclipse.datatools.sqltools.parsers.sql_1.0.2.v201107221520.jar
org.eclipse.datatools.sqltools.result_1.1.6.v201402080246.jar
org.eclipse.emf.common_2.11.0.v20150512-0501.jar
org.eclipse.emf.ecore.change_2.11.0.v20150512-0501.jar
org.eclipse.emf.ecore.xmi_2.11.0.v20150512-0501.jar
org.eclipse.emf.ecore_2.11.0.v20150512-0501.jar
org.eclipse.equinox.app_1.3.300.v20150423-1356.jar
org.eclipse.equinox.common_3.7.0.v20150402-1709.jar
org.eclipse.equinox.preferences_3.5.300.v20150408-1437.jar
org.eclipse.equinox.registry_3.6.0.v20150318-1503.jar
org.eclipse.help_3.6.0.v20130326-1254.jar
org.eclipse.osgi.services_3.5.0.v20150519-2006.jar
org.eclipse.osgi_3.10.100.v20150529-1857.jar
org.eclipse.update.configurator_3.3.300.v20140518-1928.jar
org.mozilla.javascript_1.7.5.v201504281450.jar
org.w3c.css.sac_1.3.1.v200903091627.jar
org.w3c.dom.events_3.0.0.draft20060413_v201105210656.jar
org.w3c.dom.smil_1.0.1.v200903091627.jar
org.w3c.dom.svg_1.1.0.v201011041433.jar
应用程序属性(application.yml)
birt:
report:
output:
dir: ${birt_temp_file_output_dir}
input:
dir: ${birt_report_input_dir}
logging:
level: ${birt_logging_level}
server:
port: 8080
context-path: /simple-birt-runner
birt:
logging:
level: SEVERE
logging:
level:
org:
springframework:
web: ERROR
用于运行的Dockerfile(Dockerfile)
FROM java:openjdk-8u66-jre
MAINTAINER Kent O. Johnson <[email protected] /cdn-cgi/l/email-protection>
COPY com.example.simple-birt-runner-*.jar /opt/soft/simple-birt-runner.jar
RUN mkdir -p /reports/input \
&& mkdir /reports/output \
&& mkdir -p /reports/log/engine
WORKDIR /opt/soft
CMD java -jar -Xms128M -Xmx4G simple-birt-runner.jar
EXPOSE 8080
用于运行映像的 Docker 撰写文件 (docker-compose.yml)
simple-birt-runner:
image: soft/simple-birt-runner-release
ports:
- "8090:8080"
environment:
- birt_temp_file_output_dir=/reports/output
- birt_report_input_dir=/reports/input
- birt_logging_directory=/reports/log/engine
- birt_logging_level=SEVERE
volumes_from:
- birt-report-runner-data