Right now, Spring (MVC) out-of-the-box supports both Excel/PDF and JasperReports views. The PDF/excel ones are useful but as the complexity increases building documents with iText becomes harder and harder. JasperReports offers a powerful alternative based on XML reports that can be designed with a GUI. There's still another tool in the Open Source Java field to create custom reports, Birt. I'm not going to compare it with Jasper as that has been made several times before and it's way beyond the scope of this entry.
But if someone finally decides to stick with Birt and is using Spring then he has to know that specific support in the framework won't be available until version 2.2 (see Jira) which could take sometime yet. Fortunately, a good solution can be implemented without much effort right now.
The first thing to do is understanding the Birt runtime engine. Birt is composed of several modules and the runtime environment is one of them. It can be downloaded separately and installed on its own. Once downloaded, there are some steps to follow to deploy it in a web application. The final structure should be like this:

Once deployed it can be integrated with Spring. The first consideration to make is deciding what modules of the runtime engine are going to be initialized. There are two possibilities (we'll leave the Chart Engine for later): the report engine and the design engine. They can be started independently or both in common. The report engine is in charge of processing predefined reports and documents. The design engine will allow a developer to create a report from scratch (programmatically) or modify a previous report. Reports created and modified by the design engine can be transformed later with the report engine.
To start the report engine you need to declare a simple bean with start and shutdown methods. Even though the Birt engine is hefty it will take very little time to load.
But if someone finally decides to stick with Birt and is using Spring then he has to know that specific support in the framework won't be available until version 2.2 (see Jira) which could take sometime yet. Fortunately, a good solution can be implemented without much effort right now.
The first thing to do is understanding the Birt runtime engine. Birt is composed of several modules and the runtime environment is one of them. It can be downloaded separately and installed on its own. Once downloaded, there are some steps to follow to deploy it in a web application. The final structure should be like this:

Once deployed it can be integrated with Spring. The first consideration to make is deciding what modules of the runtime engine are going to be initialized. There are two possibilities (we'll leave the Chart Engine for later): the report engine and the design engine. They can be started independently or both in common. The report engine is in charge of processing predefined reports and documents. The design engine will allow a developer to create a report from scratch (programmatically) or modify a previous report. Reports created and modified by the design engine can be transformed later with the report engine.
To start the report engine you need to declare a simple bean with start and shutdown methods. Even though the Birt engine is hefty it will take very little time to load.
public void startReportEngine(ServletContext servletContext) throws Exception {
EngineConfig config = new EngineConfig();
config.setEngineHome("");
config.setLogConfig(null, Level.ALL);
IPlatformContext context = new PlatformServletContext(servletContext);
config.setPlatformContext(context);
Platform.startup(config);
IReportEngineFactory factory = (IReportEngineFactory)
Platform.createFactoryObject(
IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
reportEngine = factory.createReportEngine(config);
}
public void shutdown() {
if (reportEngine != null) reportEngine.shutdown();
Platform.shutdown();
}
EngineConfig config = new EngineConfig();
config.setEngineHome("");
config.setLogConfig(null, Level.ALL);
IPlatformContext context = new PlatformServletContext(servletContext);
config.setPlatformContext(context);
Platform.startup(config);
IReportEngineFactory factory = (IReportEngineFactory)
Platform.createFactoryObject(
IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
reportEngine = factory.createReportEngine(config);
}
public void shutdown() {
if (reportEngine != null) reportEngine.shutdown();
Platform.shutdown();
}
The code above is entirely taken from the Birt documentation. Remember that those methods will be called when Spring is building the context so they will be executed just once. To process a report (deployed in the Reports directory) just need to add:
public byte[] createPDF(IReportRunnable design) throws Exception {
PDFRenderContext renderContext = new PDFRenderContext();
HashMap contextMap = new HashMap();
contextMap.put(EngineConstants.APPCONTEXT_PDF_RENDER_CONTEXT, renderContext);
HTMLRenderOption options = new HTMLRenderOption();
ByteArrayOutputStream out = new ByteArrayOutputStream();
options.setOutputStream(out);
options.setOutputFormat("pdf");
IRunAndRenderTask task = reportEngine.createRunAndRenderTask(design);
task.setAppContext(contextMap);
task.setRenderOption(options);
task.run();
task.close();
return out.toByteArray();
}
PDFRenderContext renderContext = new PDFRenderContext();
HashMap
contextMap.put(EngineConstants.APPCONTEXT_PDF_RENDER_CONTEXT, renderContext);
HTMLRenderOption options = new HTMLRenderOption();
ByteArrayOutputStream out = new ByteArrayOutputStream();
options.setOutputStream(out);
options.setOutputFormat("pdf");
IRunAndRenderTask task = reportEngine.createRunAndRenderTask(design);
task.setAppContext(contextMap);
task.setRenderOption(options);
task.run();
task.close();
return out.toByteArray();
}
That method returns a PDF as a byte[]. If you need another format just modify it (very easy to do). It takes a IReportRunnable design as a parameter. This is very useful as it allows it to be called from a newly created design or a report file
public byte[] createPDF(String reportName) throws Exception {
return createPDF(
reportEngine.openReportDesign(sc.getRealPath("/Reports/" + reportName)));
}
public byte[] createPDF(ReportDesignHandle reportDesign) throws Exception {
return createPDF(reportEngine.openReportDesign(reportDesign));
}
return createPDF(
reportEngine.openReportDesign(sc.getRealPath("/Reports/" + reportName)));
}
public byte[] createPDF(ReportDesignHandle reportDesign) throws Exception {
return createPDF(reportEngine.openReportDesign(reportDesign));
}
And that's all really. Don't forget to pass the ServletContext on initialization of course! I do it by making the bean ApplicationContextAware.
It's possible that some parameters are also required. Here's a little modification that allows them:
It's possible that some parameters are also required. Here's a little modification that allows them:
public byte[] createPDF(IReportRunnable design, Properties parameters)
throws Exception {
IRunAndRenderTask runAndRenderTask =
reportEngine.createRunAndRenderTask(design);
for (Object param : parameters.keySet())
runAndRenderTask.setParameterValue((String) param,
parameters.getProperty((String) param));
...
}
throws Exception {
IRunAndRenderTask runAndRenderTask =
reportEngine.createRunAndRenderTask(design);
for (Object param : parameters.keySet())
runAndRenderTask.setParameterValue((String) param,
parameters.getProperty((String) param));
...
}
If you are using Spring MVC to return the PDF to the client just subclass AbstractView and use a ResourceBundleViewResolver
public class BIRTPDF extends AbstractView {
public BIRTPDF() {
setContentType("application/pdf");
}
protected void buildPdfDocument(
Map model,
Document document,
PdfWriter pdfWriter,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
byte[] bytes = (byte[]) model.get("pdf");
httpServletResponse.getOutputStream().write(bytes);
httpServletResponse.getOutputStream().close();
httpServletResponse.getOutputStream().flush();
}
protected void renderMergedOutputModel(
Map map, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
buildPdfDocument(map, null, null, httpServletRequest, httpServletResponse);
}
}
public BIRTPDF() {
setContentType("application/pdf");
}
protected void buildPdfDocument(
Map model,
Document document,
PdfWriter pdfWriter,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
byte[] bytes = (byte[]) model.get("pdf");
httpServletResponse.getOutputStream().write(bytes);
httpServletResponse.getOutputStream().close();
httpServletResponse.getOutputStream().flush();
}
protected void renderMergedOutputModel(
Map map, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
buildPdfDocument(map, null, null, httpServletRequest, httpServletResponse);
}
}
The design engine is even easier to integrate. Just add a new bean with the following code:
private IDesignEngine designEngine = null;
public void startDesignEngine() throws Exception {
DesignConfig config = new DesignConfig();
config.setBIRTHome("");
Platform.startup(config);
IDesignEngineFactory factory = (IDesignEngineFactory)
Platform.createFactoryObject(
IDesignEngineFactory.EXTENSION_DESIGN_ENGINE_FACTORY);
designEngine = factory.createDesignEngine(config);
}
public void startDesignEngine() throws Exception {
DesignConfig config = new DesignConfig();
config.setBIRTHome("");
Platform.startup(config);
IDesignEngineFactory factory = (IDesignEngineFactory)
Platform.createFactoryObject(
IDesignEngineFactory.EXTENSION_DESIGN_ENGINE_FACTORY);
designEngine = factory.createDesignEngine(config);
}
And use the following code to create a new report programmatically
public ReportDesignHandle createReport() throws Exception {
SessionHandle session = designEngine.newSessionHandle(ULocale.ENGLISH);
ReportDesignHandle reportDesignHandle = session.createDesign();
return reportDesignHandle;
}
SessionHandle session = designEngine.newSessionHandle(ULocale.ENGLISH);
ReportDesignHandle reportDesignHandle = session.createDesign();
return reportDesignHandle;
}
Finally, I must add that there is another approach here that you may prefer (though it's a bit more complicated IMHO).

1 comentarios:
Hi! Your blog is simply super. you have create a differentiate. Thanks for the sharing this website. it is very useful professional knowledge. Great idea you know about company background.
Customized application development
Publicar un comentario en la entrada