Pentaho Reporting and Spring

At the core of the Spring Framework is dependency injection. The benefits of dependency injection include declarative configuration and enhanced testability. You can use the Spring Framework to "wire" the various Pentaho Reporting beans that make up your embedded reporting solution. This document describes a simple example application which uses Pentaho Reporting and the Spring Framework to create a report from an existing report definition and table of data.

A runnable version of this example is attached to this page. A zip containing an Eclipse project containing this example is also attached to this page.

Prerequisites

  • Java SE Development Kit (JDK) 5 or later.
  • Pentaho Reporting 0.8.9.8 or later and supporting libraries.
  • Spring Framework 2.x and supporting libraries.

Some definitions:

  • report definition: an XML file that describes the report template
  • table model: an interface for accessing any data in a tabular format (i.e. rows and columns)

Instructions

Let's work in a top down fashion. First, we'll create a class called Main.java with a main() method. This class creates a Spring application context and retrieves a bean named springDemo. The doDemo() method does the rest.

Main.java
package org.pentaho.reporting.springdemo;

import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Entry point.
 *
 * <p>This is the only class that is Spring-aware (i.e. interacts with the application context).</p>
 *
 * @author mlowery
 */
public class Main {

  public static void main(String[] args) throws Exception {
    // get the app context; must use "file:" prefix!
    FileSystemXmlApplicationContext appCtx = new FileSystemXmlApplicationContext("file:beans.xml");

    // get the bean that does the work
    SpringDemo springDemo = (SpringDemo) appCtx.getBean("springDemo");

    // run the demo
    springDemo.doDemo();
  }
}

Now is a good time to discuss the inputs to the reporting system. The minimal inputs (dependencies) to Pentaho Reporting are:

dependency

description

report definition

an XML file describing headers, row content, etc

data

accessed via a table model (see above definition in Prerequisites)

output*

where to put the resulting report output

report parser

which implementation will parse the report definition

* For the purposes of this example, the output generator (the implementation that generates the report output) and the output format (e.g. PDF) are hard-coded.

In SpringDemo.java, you'll see instance variables along with setter methods for each of these dependencies. The table below shows how the dependencies in the preceding table relate to instance variables in SpringDemo.java.

dependency

instance variable

report definition

reportResource

data

tableDataFactory

output

outputResource

report parser

reportGenerator

The doDemo() method parses the report definition, sets the data for the report, and generates the output.

SpringDemo.java
package org.pentaho.reporting.springdemo;

import java.io.IOException;

import org.jfree.report.JFreeReport;
import org.jfree.report.TableDataFactory;
import org.jfree.report.modules.output.pageable.pdf.PdfReportUtil;
import org.jfree.report.modules.parser.base.ReportGenerator;
import org.jfree.xml.ParseException;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.io.Resource;

/**
 * Uses Pentaho Reporting and Spring to generate a report.
 *
 * <p>An instance of this class is instantiated and configured via Spring. Note that this class uses Spring
 * annotations.</p>
 *
 * @author mlowery
 */
public class SpringDemo {

  private Resource reportResource;

  private ReportGenerator reportGenerator;

  private Resource outputResource;

  private TableDataFactory tableDataFactory;

  public void doDemo() throws ParseException, IOException {

    JFreeReport report = null;
    try {
      // Load and parse the report
      report = reportGenerator.parseReport(reportResource.getURL());
    } catch (Exception e) {
      throw new ParseException("Failed to parse the report", e);


    }

    // Set the source of data for the report
    report.setDataFactory(tableDataFactory);

    // Create the report and export to the supplied output filename
    PdfReportUtil.createPDF(report, outputResource.getFile().getAbsolutePath());
  }

  @Required
  public void setReportResource(Resource reportResource) {
    this.reportResource = reportResource;
  }

  @Required
  public void setReportGenerator(ReportGenerator reportGenerator) {
    this.reportGenerator = reportGenerator;
  }

  @Required
  public void setOutputResource(Resource outputResource) {
    this.outputResource = outputResource;
  }

  @Required
  public void setTableDataFactory(TableDataFactory tableDataFactory) {
    this.tableDataFactory = tableDataFactory;
  }

}

Here's the report definition.

spring-demo-report-def.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE report PUBLIC "-//JFreeReport//DTD report definition//EN//simple/version 0.8.5" "http://jfreereport.sourceforge.net/report-085.dtd">

<report name="Country" orientation="portrait" pageformat="LETTER" leftmargin="10" rightmargin="10" topmargin="10" bottommargin="10">
  <configuration>
    <property name="org.jfree.report.modules.output.table.html.Encoding">UTF-8</property>
    <property name="org.jfree.report.modules.output.pageable.pdf.Encoding">UTF-8</property>
  </configuration>

  <groups>
    <group name="fruit">
      <fields>
        <field>fruit</field>
      </fields>

      <groupheader name="GroupHeader" repeat="false" pagebreak-before-print="false" pagebreak-after-print="false" color="#000000" fontname="SansSerif" \
        fontsize="11" fontstyle="bold" height="38">
        <rectangle color="#FFFFFF" draw="false" fill="false" height="16" width="100%" x="0%" y="0"/>
        <message-field height="16" vertical-alignment="top" alignment="left" width="100%" x="0%" y="0" nullstring="-">Fruit: $(fruit)</message-field>
        <rectangle color="#C0C0C0" draw="false" fill="true" height="18" width="100%" x="0%" y="18"/>
        <rectangle draw="false" fill="false" height="2" width="100%" x="0%" y="16"/>
        <label color="#000000" fontname="SansSerif" fontsize="12" fontstyle="bold" height="18" vertical-alignment="middle" alignment="left" width="50.0%" \
          x="0.0%" y="18">Fruit</label>
        <label color="#000000" fontname="SansSerif" fontsize="12" fontstyle="bold" height="18" vertical-alignment="middle" alignment="left" width="50.0%" \
          x="50.0%" y="18">Number</label>
      </groupheader>

      <groupfooter name="GroupFooter" color="#000000" fontname="SansSerif" fontsize="11" fontstyle="bold" pagebreak-before-print="false" \
        pagebreak-after-print="false"/>
    </group>
  </groups>

  <items color="#000000" fontname="SansSerif" fontsize="9" fontstyle="bold">
    <rectangle name="rowBandingElement" color="#E0E0E0" draw="false" fill="true" height="14" x="0%" width="100%" y="0"/>
    <string-field name="fruit" nullstring="-" fieldname="fruit" vertical-alignment="middle" alignment="left" width="50.0%" x="0.0%" y="0" height="14" \
      color="#000000" fontname="Arial" fontsize="10" fontstyle="plain"/>
    <string-field name="number" nullstring="-" fieldname="number" vertical-alignment="middle" alignment="left" width="50.0%" x="50.0%" y="0" height="14" \
      color="#000000" fontname="Arial" fontsize="10" fontstyle="plain"/>
  </items>
</report>

For the purposes of this example, we've simply created a class (a table model) that directly contains the data.

SpringDemoData.java
package org.pentaho.reporting.springdemo;

import javax.swing.table.AbstractTableModel;

/**
 * Static table model for demo purposes.
 *
 * @author mlowery
 */
public class SpringDemoData extends AbstractTableModel {
  private static String[] COLUMN_NAMES = { "fruit", "number", "fruit" };

  private static String[][] DATA = { { "Apple", "One", "Apple" },
      { "Apple", "Two", "Apple" }, { "Apple", "Three", "Apple" },
     { "Orange", "Four", "Orange" }, { "Orange", "Five", "Orange" }, };

  public int getRowCount() {
    return DATA.length;
  }

  public int getColumnCount() {
    return DATA[0].length;
  }

  public Object getValueAt(int rowIndex, int columnIndex) {
    if (rowIndex >= 0 && rowIndex < DATA.length) {
      if (columnIndex >= 0 && columnIndex < DATA[~mlowery:rowIndex].length) {
        return DATA[~mlowery:rowIndex][~mlowery:columnIndex];
      }
    }
    return null;
  }

  public String getColumnName(final int column) {
    if (column >= 0 && column < COLUMN_NAMES.length) {
      return COLUMN_NAMES[~mlowery:column];
    }
    return null;
  }

}

Now how are all these things wired together? The Spring XML file below shows how.

beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <!-- reporting system -->
  <bean id="jFreeReportBoot" class="org.jfree.report.JFreeReportBoot"
    factory-method="getInstance" init-method="start" />
  <!-- report generator -->
  <bean id="reportGenerator" class="org.jfree.report.modules.parser.base.ReportGenerator"
    factory-method="getInstance" />
  <!-- TableModel instance -->
  <bean id="springDemoData" class="org.pentaho.reporting.springdemo.SpringDemoData" />
  <!-- table data factory -->
  <bean id="tableDataFactory" class="org.jfree.report.TableDataFactory">
    <constructor-arg value="default" />
    <constructor-arg ref="springDemoData" />
  </bean>
  <!--
    depends-on attribute is to make sure that reporting system is
    "booted" before this bean is created
  -->
  <bean id="springDemo" class="org.pentaho.reporting.springdemo.SpringDemo"
    depends-on="jFreeReportBoot">
    <property name="reportGenerator" ref="reportGenerator" />
    <!-- uses resource framework of Spring -->
    <property name="reportResource" value="classpath:spring-demo-report-def.xml" />
    <!-- uses resource framework of Spring -->
    <property name="outputResource" value="file:/tmp/spring-demo-report.pdf" />
    <property name="tableDataFactory" ref="tableDataFactory" />
  </bean>
</beans>

Results

Running the Main class will produce a file called spring-demo-report.pdf.

This example demonstrates basic Spring Framework dependency injection, along with Spring annotations, resources, and depends-on. (See Related Items.)

Troubleshooting

  • Report definition file cannot be found.
    The example assumes that spring-demo-report-def.xml is somewhere in your classpath.
  • Report output cannot be written.
    The example assumes that the PDF output will be written to /tmp. Either make sure that this is an accurate absolute path for your system (and you have write access) or change the value.

Related Items

A runnable version of this example is attached to this page. A zip containing an Eclipse project containing this example is also attached to this page.