16.7 JasperReports

JasperReports (http://jasperreports.sourceforge.net) is a powerful open-source reporting engine that supports the creation of report designs using an easily understood XML file format. JasperReports is capable of rendering reports output into four different formats: CSV, Excel, HTML and PDF.

16.7.1 Dependencies

Your application will need to include the latest release of JasperReports, which at the time of writing was 0.6.1. JasperReports itself depends on the following projects:

  • BeanShell

  • Commons BeanUtils

  • Commons Collections

  • Commons Digester

  • Commons Logging

  • iText

  • POI

JasperReports also requires a JAXP compliant XML parser.

16.7.2 Configuration

To configure JasperReports views in your Spring container configuration you need to define a ViewResolver to map view names to the appropriate view class depending on which format you want your report rendered in.

16.7.2.1 Configuring the ViewResolver

Typically, you will use the ResourceBundleViewResolver to map view names to view classes and files in a properties file.

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
</bean>

Here we've configured an instance of the ResourceBundleViewResolver class that will look for view mappings in the resource bundle with base name views. (The content of this file is described in the next section.)

16.7.2.2 Configuring the Views

The Spring Framework contains five different View implementations for JasperReports, four of which correspond to one of the four output formats supported by JasperReports, and one that allows for the format to be determined at runtime:

Table 16.2. JasperReports View classes

Class NameRender Format
JasperReportsCsvViewCSV
JasperReportsHtmlViewHTML
JasperReportsPdfViewPDF
JasperReportsXlsViewMicrosoft Excel
JasperReportsMultiFormatViewThe view is decided upon at runtime

Mapping one of these classes to a view name and a report file is a matter of adding the appropriate entries into the resource bundle configured in the previous section as shown here:

simpleReport.class=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper

Here you can see that the view with name simpleReport is mapped to the JasperReportsPdfView class, causing the output of this report to be rendered in PDF format. The url property of the view is set to the location of the underlying report file.

16.7.2.3 About Report Files

JasperReports has two distinct types of report file: the design file, which has a .jrxml extension, and the compiled report file, which has a .jasper extension. Typically, you use the JasperReports Ant task to compile your .jrxml design file into a .jasper file before deploying it into your application. With the Spring Framework you can map either of these files to your report file and the framework will take care of compiling the .jrxml file on the fly for you. You should note that after a .jrxml file is compiled by the Spring Framework, the compiled report is cached for the lifetime of the application. To make changes to the file you will need to restart your application.

16.7.2.4 Using JasperReportsMultiFormatView

The JasperReportsMultiFormatView allows for report format to be specified at runtime. The actual rendering of the report is delegated to one of the other JasperReports view classes - the JasperReportsMultiFormatView class simply adds a wrapper layer that allows for the exact implementation to be specified at runtime.

The JasperReportsMultiFormatView class introduces two concepts: the format key and the discriminator key. The JasperReportsMultiFormatView class uses the mapping key to lookup the actual view implementation class and uses the format key to lookup up the mapping key. From a coding perspective you add an entry to your model with the formay key as the key and the mapping key as the value, for example:

public ModelAndView handleSimpleReportMulti(HttpServletRequest request,
HttpServletResponse response) throws Exception {

  String uri = request.getRequestURI();
  String format = uri.substring(uri.lastIndexOf(".") + 1);

  Map model = getModel();
  model.put("format", format);

  return new ModelAndView("simpleReportMulti", model);
}

In this example, the mapping key is determined from the extension of the request URI and is added to the model under the default format key: format. If you wish to use a different format key then you can configure this using the formatKey property of the JasperReportsMultiFormatView class.

By default the following mapping key mappings are configured in JasperReportsMultiFormatView:

Table 16.3. JasperReportsMultiFormatView Default Mapping Key Mappings

Mapping KeyView Class
csvJasperReportsCsvView
htmlJasperReportsHtmlView
pdfJasperReportsPdfView
xlsJasperReportsXlsView

So in the example above a request to URI /foo/myReport.pdf would be mapped to the JasperReportsPdfView class. You can override the mapping key to view class mappings using the formatMappings property of JasperReportsMultiFormatView.

16.7.3 Populating the ModelAndView

In order to render your report correctly in the format you have chosen, you must supply Spring with all of the data needed to populate your report. For JasperReports this means you must pass in all report parameters along with the report datasource. Report parameters are simple name/value pairs and can be added be to the Map for your model as you would add any name/value pair.

When adding the datasource to the model you have two approaches to choose from. The first approach is to add an instance of JRDataSource or a Collection type to the model Map under any arbitrary key. Spring will then locate this object in the model and treat it as the report datasource. For example, you may populate your model like so:

private Map getModel() {
  Map model = new HashMap();
  Collection beanData = getBeanData();
  model.put("myBeanData", beanData);
  return model;
}

The second approach is to add the instance of JRDataSource or Collection under a specific key and then configure this key using the reportDataKey property of the view class. In both cases Spring will instances of Collection in a JRBeanCollectionDataSource instance. For example:

private Map getModel() {
  Map model = new HashMap();
  Collection beanData = getBeanData();
  Collection someData = getSomeData();
  model.put("myBeanData", beanData);
  model.put("someData", someData);
  return model;
}

Here you can see that two Collection instances are being added to the model. To ensure that the correct one is used, we simply modify our view configuration as appropriate:

simpleReport.class=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper
simpleReport.reportDataKey=myBeanData

Be aware that when using the first approach, Spring will use the first instance of JRDataSource or Collection that it encounters. If you need to place multiple instances of JRDataSource or Collection into the model then you need to use the second approach.

16.7.4 Working with Sub-Reports

JasperReports provides support for embedded sub-reports within your master report files. There are a wide variety of mechanisms for including sub-reports in your report files. The easiest way is to hard code the report path and the SQL query for the sub report into your design files. The drawback of this approach is obvious - the values are hard-coded into your report files reducing reusability and making it harder to modify and update report designs. To overcome this you can configure sub-reports declaratively and you can include additional data for these sub-reports directly from your controllers.

16.7.4.1 Configuring Sub-Report Files

To control which sub-report files are included in a master report using Spring, your report file must be configured to accept sub-reports from an external source. To do this you declare a parameter in your report file like so:

<parameter name="ProductsSubReport" class="net.sf.jasperreports.engine.JasperReport"/>

Then, you define your sub-report to use this sub-report parameter:

<subreport>
    <reportElement isPrintRepeatedValues="false" x="5" y="25" width="325"
        height="20" isRemoveLineWhenBlank="true" backcolor="#ffcc99"/>
    <subreportParameter name="City">
        <subreportParameterExpression><![CDATA[$F{city}]]></subreportParameterExpression>
    </subreportParameter>
    <dataSourceExpression><![CDATA[$P{SubReportData}]]></dataSourceExpression>
    <subreportExpression class="net.sf.jasperreports.engine.JasperReport">
                  <![CDATA[$P{ProductsSubReport}]]></subreportExpression>
</subreport>

This defines a master report file that expects the sub-report to be passed in as an instance of net.sf.jasperreports.engine.JasperReports under the parameter ProductsSubReport. When configuring your Jasper view class, you can instruct Spring to load a report file and pass into the JasperReports engine as a sub-report using the subReportUrls property:

<property name="subReportUrls">
    <map>
        <entry key="ProductsSubReport" value="/WEB-INF/reports/subReportChild.jrxml"/>
    </map>
</property>

Here, the key of the Map corresponds to the name of the sub-report parameter in th report design file, and the entry is the URL of the report file. Spring will load this report file, compiling it if necessary, and will pass into the JasperReports engine under the given key.

16.7.4.2 Configuring Sub-Report Data Sources

This step is entirely optional when using Spring configure your sub-reports. If you wish, you can still configure the data source for your sub-reports using static queries. However, if you want Spring to convert data returned in your ModelAndView into instances of JRDataSource then you need to specify which of the parameters in your ModelAndView Spring should convert. To do this configure the list of parameter names using the subReportDataKeys property of the your chosen view class:

<property name="subReportDataKeys"
    value="SubReportData"/>

Here, the key you supply MUST correspond to both the key used in your ModelAndView and the key used in your report design file.

16.7.5 Configuring Exporter Parameters

If you have special requirements for exporter configuration - perhaps you want a specific page size for your PDF report, then you can configure these exporter parameters declaratively in your Spring configuration file using the exporterParameters property of the view class. The exporterParameters property is typed as Map and in your configuration the key of an entry should be the fully-qualified name of a static field that contains the exporter parameter definition and the value of an entry should be the value you want to assign to the parameter. An example of this is shown below:

<bean id="htmlReport" class="org.springframework.web.servlet.view.jasperreports.JasperReportsHtmlView">
  <property name="url" value="/WEB-INF/reports/simpleReport.jrxml"/>
  <property name="exporterParameters">
    <map>
      <entry key="net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER">
        <value>Footer by Spring!
          &lt;/td&gt;&lt;td width="50%"&gt;&amp;nbsp; &lt;/td&gt;&lt;/tr&gt;
          &lt;/table&gt;&lt;/body&gt;&lt;/html&gt;
        </value>
      </entry>
    </map>
  </property>
</bean>

Here you can see that the JasperReportsHtmlView is being configured with an exporter parameter for net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER which will output a footer in the resulting HTML.