Friday, February 29, 2008

Flexible reporting with JasperReports and iBATIS

Integrate JasperReports with your existing iBATIS implementation

The core task of many Java applications is to retrieve data and display it, sometimes in sophisticated print- or Web-based reports. Luckily for Java developers, two popular open source solutions work especially well together to help you accomplish this task. The iBATIS Data Mapper framework provides a simple XML-based mechanism for linking Java objects to a data repository. JasperReports is a full featured Java reporting library that you can embed in your applications. Put the two together and you have a winning combination for producing scalable, easy-to-maintain reports.

JasperReports is an open source Java reporting library that is quickly gaining popularity as a viable alternative to costly proprietary reporting solutions. With any reporting solution, getting the data to the reporting engine is the most basic implementation concern. Unfortunately, Jasper poses a small problem in this area.

Most Java applications use some type of data-fetching framework for data mapping and dynamic SQL generation, such as the iBatis Data Mapper Framework. Jasper's default mechanism for retrieving and managing data isn't flexible enough to leverage existing data mapping frameworks, however. Instead, you pass the Jasper engine a connection to your database, and it uses SQL queries embedded in an XML-based report template to populate the report.

Although simple to implement, this mechanism ties you to the Jasper template's embedded SQL. Besides, who wants to add yet another moving piece to an already complex application? You would be better off leveraging the existing data framework and just letting Jasper handle report generation.

In this article you'll learn how to integrate JasperReports and the iBATIS Data Mapper Framework for just such a solution. I'll walk through two simple scenarios where the goal is to integrate Jasper and iBATIS for report generation. The first scenario applies to iBATIS implementations that use iBATIS's data capabilities to return a list of Java beans. This scenario doesn't require you to write any custom code. The Jasper framework contains supporting classes that allow the data returned from iBATIS to fill a Jasper report.

For the second scenario -- a more basic uses of iBATIS that returns a list of java.util.Map objects -- you'll create a custom Jasper data source to feed a Jasper report. In addition to working with the Jasper framework classes, for both exercises you'll use the iReport report designer, which eases and accelerates the process of creating template files in Jasper.

Running the examples
This article's example code generates a simple monthly sales report for each type of implementation I cover. The data for the reports is retrieved from an embedded Apache Derby database via the iBATIS Data Mapper framework. The examples are built into a JSF/Spring-based Web application that runs in the same JVM as Derby. I've provided an Ant script for building that WAR file -- just execute the buildWar task to compile content and build it. You'll need Tomcat 5.5x to deploy and run the examples. You'll also need the Abode Acrobat Reader Web browser plug-in to view the report output.

Getting the iBATIS data into Jasper
Using iBATIS to return a list of a specific type of Java beans (I'll call this a return list) is much tidier than using the framework to return a list of java.util.Map objects. Most developers using iBATIS take this approach to data mapping, and it happens to make integration with Jasper a snap.

The Jasper framework provides a JRDataSource implementation that your application can use to fill a report template with data from an iBATIS return list. The JRBeanCollectionDataSource class is constructed from a collection of Java beans and knows how to loop through the collection and access the beans' properties. Listing 1 shows how you can pass an instance of a JRBeanCollectionDataSource when calling on the Jasper engine to populate a report.

Listing 1. Populating a report with JRBeanCollectionDataSource
/* Helper method to create a fully populated JasperPrint object from an list of Java beans */
private JasperPrint fillReport (List dataList) throws JRException {

// this map could be filled with parameters defined in the report
Map parameters = new HashMap();

// make sure the .jasper file (a compiled version of the .jrxml template file) exists
String localPath = this.servlet.getServletContext().getRealPath("/");

File reportFile = new File(localPath + "WEB-INF" + File.separator + "monthySales.jasper");

if (!reportFile.exists()) {
throw new JRRuntimeException("monthySales.jasper file not found.");
}

// load up the report
JasperReport jasperReport = (JasperReport)JRLoader.loadObject(reportFile);

// pass JRBeanCollectionDataSource (which is populated with iBATIS list) to fillReport method
return JasperFillManager.fillReport (jasperReport, parameters,
new JRBeanCollectionDataSource (dataList));
}


In Listing 1, you first define the parameters map, which is the mechanism for passing parameter values to the report at runtime. For example, you could define a parameter named REPORT_TITLE in the report template and pass the value for this parameter to the report by simply adding the key/value pair to the map (e.g., Key=REPORT_TITLE, Value=Sale Report). The parameters map is passed to the fillReport method. The next portion of code loads a compiled Jasper template (.jasper) file. Finally, the static fillReport method is called. It does the actual work of building the report and returns a JasperPrint object, which is passed to a specific type of Jasper exporter to write out the report. The example code for this article uses a JRPdfExporter to write the report to PDF format (see the PdfServlet.java class).

Although this mechanism lets the Jasper framework link with iBATIS, you might need to modify the Java beans that iBATIS populates, depending on your report's requirements. Jasper's field objects know how to work with the common JDBC mapping types. For example, Jasper stores an Oracle numeric field type as a java.math.BigDecimal object. Any of the iBATIS bean properties that you plan to use in a report must map to one of Jasper's defined field types. You should select your report field types carefully, because the formatting and expression capabilities are better in some types than in others. For example, a BigDecimal type is more convenient to work with than a String when you're trying to apply a currency format.

No comments: