Generating XML templates for JasperReports using Rational Application Developer

JasperReports helps organizations generate affordable business data reports using an XML template. The XML template provides the key report information such as the SQL query, report title, column headers, and database fields. This article discusses how to generate the XML template to allow a user to specify which report columns should be included.

Ricardo Olivieri (roliv@us.ibm.com), Software Engineer, IBM

Ricardo OlivieriRicardo Olivieri is a Software Engineer in IBM Global Services. He is part of the development team for the Mixed Address Database application (an internal registration tool for IBM systems and network infrastructure devices). His areas of expertise include design and development of enterprise Java applications for WebSphere Application Server, administration and configuration of WebSphere Application Server, and distributed software architectures. He is a certified Java developer and a certified WebSphere Application Server administrator. He has a B.S. in computer engineering from the University of Puerto Rico. You can reach Ricardo at roliv@us.ibm.com.



Jorge D Rodriguez (jorgedr@us.ibm.com), Software Engineer, IBM

Jorge D. Rodriguez is a software engineer in IBM System and Technology Group. He is currently working on the development of reporting tools used internally by the AIX Development organization. Jorge is a Java certified programmer and his areas of expertise include Java application design and development. He has a B.S. in computer engineering from the University of Puerto Rico. You can reach Jorge at jorgedr@us.ibm.com.



25 May 2005

Introduction

JasperReports is an open-source report engine that is being used by many organizations that need to generate business data reports. Having the ability to generate reports on business data is crucial since the organization uses these reports to make effective decisions.

People that use these reports often have different roles and analyze different portions of the data. For instance, a customer report that shows the name, address, age, email, and number of orders for each customer might be useful for a certain group of people while others prefer having a customer report that shows only the age and the number of orders for each customer. In other words, different roles within an organization need different report views.

To generate different views of a JasperReports report using, you need to understand how reports are generated. JasperReports uses an XML document that defines the report design. You use this template file to provide the reporting engine with the information it needs to create the report.

In this template file you specify the SQL query, title of the report, images to include, text for the page headers, columns that should be shown, and the database fields that make up the column definitions. For instance, a location column in a JasperReports report could be composed of two database fields: city and state.

If there is a need to show different columns of a report depending on a criteria such as user selections, it can be accomplished in several ways. One programming approach would be to create an XML template file for each possible combination of columns. Then based on the columns the user wants to see, the application will select the corresponding XML template to generate the corresponding report view. This approach is technically possible but counterproductive since the only difference among these XML templates is the section that specifies the columns to show.

A different alternative is to load the XML template into memory and then use an XML library, such as JDOM, to perform changes to the XML document based on the user selections. The application would add/remove the column definitions from the in-memory XML template. This approach is also technically possible. But managing the XML structure of the template can turn into a nightmare.

A third option is to use the JasperReports design API that is part of the JasperReports library. Instead of using an XML file to specify your report design, you use the JasperDesign class in conjunction with other classes found in the net.sf.jasperreports.engine.design package to specify the properties of your report.

Building a report design with this set of classes is similar to the process of building a set of windows and its widgets using the Swing API. First you create the JasperDesign object and then start putting the pieces together that makes up the complete report. This is a flexible approach, but the extra code and associated maintenance issues can turn into a problem later on.

The most practical option is to generate the XML template at run-time using a Java™-based templating engine. This approach requires you to write a templating file that contains the definition of the XML template. In this templating file, "placeholders" are assigned to the data that cannot be computed at compilation time. This programming approach is used if different report views are required, and the user will decide what columns they would like to see at run-time.

The templating engine we chose to use is Velocity V1.4. Velocity is an open-source Java-based templating engine that has gained popularity for its ease of use and abundance of documentation.

This article explains how to generate a JasperReports XML template from a templating file. As an aid to readers, a sample Web application is provided as a case study. This Web application uses JasperReports to generate a customer report for a fictitious company named XYZ. A customer report for company XYZ includes data such as name, email, number of orders, location, and age.

When this sample application is started, the user is given a page that lets them select what columns they want to include in the report. Users can personalize the customer report by choosing the view (for example, the columns) they are interested in seeing. The sample application can also generate an order report for company XYZ. An order report for company XYZ includes order id, order status, placement method (phone, fax, online), name of the customer who placed the order, and customer's email. Users can also choose the columns they would like to include in an order report. Both of these reports are generated from the same templating file. The properties that make each report unique are injected into the JasperReports XML template via the Velocity templating engine.

You will import the sample application into IBM® Rational® Application Developer V6 and deploy it to a WebSphere® Application Server V5.1 test instance. DB2® UDB V8.2 is used as the enterprise data repository. A SQL script file is provided to create and populate the database tables that are needed by the application.


Assumptions

You should have a working knowledge (but not necessarily mastery) of XML, HTML, SQL, Velocity, and JasperReports. You should be familiar with Rational Application Developer and the J2EE perspective, and know how to import EAR files into the workspace. Knowledge of the directory structure of an expanded EAR file is also required. You should also know how to define datasources and how to deploy an EAR application to a WebSphere Application Server V5.1 test instance. If you need introductory information to any of these technologies, please see the Resources section.


Install the sample Web application

To install the sample application, you need to download and unzip the companion ZIP file (see the Download section). Two files are included in this ZIP file: create-db.sql and JReportsEAR.ear (as well as two XML files we will use later). Before you can run the Web application, you need to create and populate a DB2 database named reportdb. To create this database, run the create-db.sql file. Use the following command to run create-db.sql:

db2 -svtf create-db.sql

The file JReportsEAR.ear contains the sample Web application. Import JReportsEAR.ear into your Rational Application Developer workspace, using the default options. A new Web project named JReportsWEB should appear in your workspace. In the WebContent/WEB-INF/lib folder of this project, you should see all the JAR files required to run the Web application (see Figure 1). This article uses JasperReports V0.6.5 -- for information on other versions, see the JasperReports home page. If you would like to know more about the dependencies JasperReports has, click on requirements on the left navigation bar of the JasperReports home page.

Figure 1. Project Explorer view in Rational Application Developer after importing sample application.
Figure 1. Project Explorer view in Rational Application Developer after importing sample application.

After importing the application, you should configure a DB2 UDB datasource for the WebSphere Application Server v5.1 test instance where you will run the sample application. Specify jdbc/reportDB as the JNDI name and reportdb as the database name in the definition for the datasource -- otherwise the application won't find the datasource and will not run.

Now you can deploy the sample application to your test server instance and start it. Once the test server instance is up and running, right-click on the JReportsWEB project and select Run => Run on Server. An HTML page named customer-report.html will open. This page shows all the columns that can be included in a customer report for company XYZ (see Figure 2) with a check box next to each column. To include a column in the report, the user selects that checkbox.

To test this feature, select columns you want to include in the report and click Generate Report. A new browser window will open showing the customized customer report. The report containing only those columns that were selected.

Figure 2. User can select one or more columns to include in a customer report.
Figure 2. User can select one or more columns to include in a customer report.

If all columns are selected, the report shown in Figure 3 should be generated.

Figure 3. View of a customer report if all columns were selected.
Figure 3. View of a customer report if all columns were selected.

As mentioned earlier, the sample application can also generate order reports. To generate an order report, right click on the order-report.html file that is found under the WebContent folder of the JReportsWEB application, and then select Run => Run on Server. Figure 4 shows the page that should be loaded into the browser.

Figure 4. User can select one or more columns to include in an order report.
Figure 4. User can select one or more columns to include in an order report.

If all columns are selected, then the report shown in Figure 5 should be generated.

Figure 5. View of an order report if all columns were selected.
Figure 5. View of an order report if all columns were selected.

Overview of JasperReports and Velocity

Nowadays, generating Web reports from business data is a common requirement for many applications. To address this requirement, software development teams will either write their own code to generate reports, buy a commercial reporting product such as Crystal Reports, or use one of the open-source reporting engines.

JasperReports is one of the most popular options for open-source reporting. It is a Java reporting engine tool that uses XML templates to generate reports that can be delivered onto the screen, to the printer, or output to PDF, HTML, XLS, CSV, and XML. If you are new to JasperReports, you might do a Web search for introductory articles on this subject. We recommend the developerWorks article titled "Generating online reports using JasperReports and WebSphere Studio."

Although JasperReports provides a great degree of flexibility in creating reports, the use of a static XML template limits the flexibility of the content as well as aspects of the overall design. Therefore you need a technology that can manipulate the XML template file at runtime in order to change the reports dynamically.

Velocity is a Java-based template engine that allows processing of text documents and generation of dynamic content using a simple template language called VTL (Velocity Template Language). Although the growing popularity for Velocity can be attributed to its use in J2EE applications and the MVC design pattern, this technology can also be applied in other programming areas. One such application is to use Velocity to generate the XML templates used by the JasperReports engine.

With Velocity you can create a templating file (also known as the VTL template) and abstract the dynamic elements. Through the use of references and directives defined by the Velocity Template Language, you can access public methods of Java objects and incorporate the result of those method invocations into the VTL template file. Understanding Velocity is out of the scope of this article, so please look at the Resources section below for more information.


Merging JasperReports with Velocity

The most common way to generate reports with JasperReports requires the creation of an XML template that serves as the design for your report. Since Velocity can manipulate the content of XML documents, merging the two technologies requires the changing the XML template used by the JasperReports engine. The VTL template will contain the structure of a JasperReports XML template along with VTL expressions spread throughout the different sections of the document.

The first VTL construct found in our sample templating file is used to specify the SQL query string of the report. Although we could have specified the SQL query with a JasperReports parameter, we decided to use a Velocity reference to allow for a more flexible design in our sample application and also to introduce the concept with a simple example. The XML code looks as follows:

<queryString>
  <![CDATA[$sql]]>
</queryString>

Notice that an expression of the Velocity Template Language is used in conjunction with JasperReports XML template elements in order to create our VTL template. Once the VTL template is parsed by the Velocity engine, the $sql reference will be replaced by the corresponding string value.

So far we have not shown how the use of Velocity can help generate dynamic reports since we could have also used a JasperReports parameter to provide the SQL query. The VTL directives that are used throughout the remaining sections of the VTL template provide the real value of using Velocity to produce the XML template file. The following piece of code demonstrates how to take advantage of the looping directive to dynamically generate the fields section of the report.

#foreach ($field in $fieldList)
  <field name="$field.name" class="$field.clazzName"></field>
#end

Once the VTL template is parsed, only fields that have been added to the Velocity context will make it to the resulting XML template file. This gives you a great flexibility in selecting the fields you need at runtime.

Another section of the JasperReports XML template that needs to be generated dynamically is the column header.

<columnHeader>
  <band height="20">
    #set( $x = 0 )
    #foreach ($column in $columnList)
      <staticText>
        <reportElement mode="Opaque" x="$x" y="5" width="$column.width"
          height="15" forecolor="#ffffff" backcolor="#bbbbbb" />
          <textElement textAlignment="Left">
            <font reportFont="Arial_Bold" />
        </textElement>
      <text>
        <![CDATA[$column.name]]>
      </text>
      </staticText>
      #set( $x = $x + $column.width )
    #end
    </band>
</columnHeader>

In order to specify at run time what columns should be included in the final report, we needed to add the column names to the column header section dynamically using the VTL looping directive. Since we are relying on a Velocity reference, only the columns that are added to the data context will make it to the JasperReports XML template. Along with the column name, you also specify the horizontal position of the data inside each column. Since the #set directive allows for mathematical expressions, we use it to keep track of the horizontal position that corresponds to each subsequent column.

Now that we saw how to dynamically generate the column header section, let us take a look at how we assemble the details section of the XML template.

#set( $x = 0 )
#foreach ($column in $columnList)
  <textField>
    <reportElement x="$x" y="4" width="$column.width" height="15" />
    <textElement textAlignment="$column.alignment" />
    <textFieldExpression class="$column.clazzType">
      <![CDATA[$column.expression]]>
    </textFieldExpression>
  </textField>
  #set( $x = $x + $column.width )
#end

Generating the detail section of the XML template is analogous to generating the column header section. Basically you use the same directives already explained without taking away the flexibility that JasperReports offers in the process. One of JasperReports's most attractive features is that it allows you to create an expression where you can combine fields from the data source with your own text and show the result of this expression as a single column during the report generation.

It is critical not to lose the flexibility provided by these expressions. Therefore, it is important to associate each column to be displayed in the report with a JasperReports text field expression. In our sample templating file, the text field expression can be obtained by using the $column.expression property. Since the $column.expression property should represent a JasperReports text field expression, we need to make sure that it follows the rules and format of such elements. For example, to make a reference to a field, we put the field name between the $F{} character sequence.

Although there are other areas in the templating file where VTL constructs are used, one particular set of directives should be discussed here. When Velocity finds the "$" character in front of another character or word, it checks to see if there is a corresponding value in the Velocity context. If the reference can't be found, the correct behavior is to output the characters as shown in the templating file.

However, when we started working with Velocity to generate JasperReports XML templates, we found out that we could not use the syntax for JasperReports' parameters freely. When Velocity parses a VTL template file that contains a JasperReports parameter expression, the $P{} character sequence is processed incorrectly and produces an invalid JasperReports XML template. To avoid this integration problem, we created a VTL reference and assigned it a value equal to the string representing the JasperReports parameter we wanted to use. This can be seen in the following snippet of code:

<title>
  #set( $baseDir = '$P{BaseDir}' )
  #set( $title = '$P{Title}' )
  #set( $imageFile = '$P{ImageFile}' )
...
</title>

Let's shift our focus from the VTL template file to the Java code used to merge the two technologies. For the most part, using Velocity as a templating tool to produce the JasperReports XML template file does not require a lot of extra code. The only issue to address during the integration of both technologies is the connection of streams.

Although it is technically possible to generate the XML template file into the file system and use JasperReports's API to read the file back from the file system, this approach will severely impact application performance. A better approach is to use Velocity's template object to generate the XML template file and pass the output straight to the JasperReports's compiler manager for processing. This concept is show in Figure 6.

Figure 6. I/O stream connection between Velocity and JasperReports.
Figure 6. I/O stream connection between Velocity and JasperReports.

Since Velocity uses character streams as the output mechanism, this output needs to be converted into a byte stream to make it compatible with the JasperReports input mechanism. Also, the JasperReports compiler manager must read data as soon as the character output stream used by template object has data available. For that reason, we decided to use piped input and output streams to transfer the data between the Velocity engine and the JasperReports compiler. The following code snippet shows how this is done.

Template template;
Context context;
PipedInputStream inStream;
BufferedWriter writer;
Thread thread;
TemplateCompiler compiler;
...

try {
  PipedOutputStream outStream = new PipedOutputStream();
  writer = new BufferedWriter(new, OutputStreamWriter(outStream));
  // Connect input stream to output stream
  inStream = new PipedInputStream(outStream);
  compiler = new TemplateCompiler(inStream);
  thread = new Thread(compiler);
  thread.start();
  template.merge(context, writer);
} catch (Exception e) {
	throw e;
} finally {
  if (writer != null) {
    writer.flush();
    writer.close();
  }
}
// Wait for thread to finish executing
thread.join();
// Get compiled report
return (compiler.getJasperReport());

The character output stream being sent as a parameter to the merge() method of the Template instance is actually directing the output to a PipedOutputStream object. This output stream is then connected to the piped input stream instance used by the JasperReports's compiler manager. Since we are using piped streams, we need to read and write the data in different threads. Otherwise, there could be a deadlock in our application.

Once assembling the chain of streams is completed, parsing of the VTL template begins and a separate thread is spawn. This thread is responsible for calling the compileReport() method of the JasperCompileManager class, which uses the output of the Velocity engine as its input. When the Velocity's template object finishes its processing, we have to wait until the thread that is responsible for compiling the XML template completes as well. After the processing is complete, we can get the JasperReports instance from the compiler manager and start using it for the generation of reports.

Now that we have an understanding on how JasperReports and Velocity can work together, let's look at the sample application.


Basic framework to generate JasperReports XML templates at run time

The sample application contains a basic framework for generating JasperReports XML templates from a VTL template. This section will focus on this framework and how it is used by the application. This framework could be extracted, modified, and used in any other application that has the need to generate JasperReports XML templates dynamically. After reading this section, you should have an understanding of what would be required if there is a need to add a new type of report to the sample application.

The design of this framework assumes that, in most cases, reports generated by a particular business organization follow a common structure regardless of the type of report (for example, customer report vs. order report) or the view of the report (for example, columns that should be included).

Our definition of report structure is the layout of the different sections that makes up a complete report. In the sample application, all types of reports have the following structure or layout: a title, an image next to the title, a page header, a column header (column names), the retrieved data, a page footer that specifies the page number, and a report summary that states how many records are found in the report.

In this framework, the layout of these sections is not configurable. This means that these sections will always appear in the report and their location is fixed. However, the framework allows configuring the data that will be shown in these sections.

The configurable data that is inserted into the different sections of the JasperReports XML template is obtained from an XML configuration file that exists for each type of report. For the sample application, we created two of these configuration files: customer-report.xml (for the customer report) and order-report.xml (for the order report). These files are included in the zip file download. If there was a requirement to add a third type of report to the application, then a new XML configuration file should be created for it. A description of the elements of the XML configuration file is included below:

report
Root element of the document.
name
Name of the report. It is used to associate a unique name with the set of properties defined in the document (think of the name as being a key and the set of properties as being the value associated with that name).
title
Title that should be shown in the report.
image
Filename of the image that should be shown next to the title. All image files are placed in the WebContent/images folder of the Web application.
header
Text that should be shown as a page header on every page.
sql
SQL query to retrieve the report data from the data source.
fields
Specifies the JasperReports data field definitions that can be included in the JasperReports XML template (data fields store values retrieved from the data source). Notice that each possible field that the report could show should be listed in this section. A name and a Java class type describe each field. As required by the JasperReports engine, each one of these fields must appear in the select clause of the SQL query and the Java class type must be compatible with the data retrieved from the data source (see the quick reference section on the JasperReports Web site for a list of the possible class types).
columns
Specifies the column definitions that can be included in the report. Each possible column that the report could show should be listed in this section. A name, a width, an alignment, a JasperReports text field expression, and a Java class type describe a column. The name is what appears in the column header; the width must be specified in pixels. Valid values for alignment are Left, Center, Right, or Justified. The JasperReports text field expression specifies what fields are used to populate the column. The Java class type specifies the class type that should be applied to the text field expression. See the quick reference section on the JasperReports Web site for a list of the possible class types for field expressions.

The properties defined in each XML configuration file are loaded into memory when the web application is started. A servlet named ReportServlet receives as an initialization parameter (reports.properties) the names of the XML configuration files that should be loaded at startup (see Figure 7).

Figure 7. XML configuration filenames are passed in as a servlet initialization parameter.
Figure 7. XML configuration filenames are passed in as a servlet initialization parameter.

To help you understand how the data in the configuration file is used, here's a description of the events that occur when a user submits a request. Assume that the user is interested in generating a customer report using the customer-report.html page.

When the user's request to generate a report is received, the application uses the value of a hidden HTTP field to determine the configuration information. The value of this hidden field must match the value of the name element in the XML configuration file. If you take a look at the customer-report.xml file, you will see that the value for the name element is 'Customer Report,' which matches the value of the hidden field in the HTML page.

Therefore, when a request to generate a report is sent from the customer-report.html page, the application uses the configuration information contained in the customer-report.xml file. The configuration information is queried to obtain the SQL query, title, image filename, header string, and definitions of the columns and fields to include in the report.

To determine what columns should be included in the report, the application uses the value of an HTTP parameter named columns. The value of this HTTP parameter depends on which columns a user selects. To specify which columns to include in a customer report, the user selects the checkboxes shown on the customer-report.html page (see Figure 2).

Each one of these checkboxes is associated with a column name that must be found among the names that appear in the columns section of the customer-report.xml file. Once the application knows what columns should be included in the report, it queries the configuration information to obtain the definitions of those columns only. The field definitions that are extracted from the configuration information also depend on the columns the user selected.

Only those fields that are required to populate the columns that were selected are extracted. For instance, if a user selects to see only the name, location, and number of orders columns, then the email and age fields are not extracted since these fields are not required to populate the name, location or number of orders columns.

The SQL query, title, header string, columns and field definitions extracted from the configuration file are placed in the Velocity context. After the Velocity context is populated, the templating engine generates the JasperReports XML template, which is then passed to the JasperReports compiler.

The only piece of data extracted from the configuration file that is not placed in the Velocity context is the filename of the image. Instead, the image filename is passed as a JasperReports parameter to the JasperReports engine.

Once the JasperReports XML template is compiled, the report is ready to be filled with data from the data source. At this point, the engine uses the SQL query in the template to query the data source and generate the report.

If you wanted to add a new type of report to the sample application, you would only need to create an XML configuration file and then an interface so users can select the columns they would like to include in the report. Also, the name of this XML configuration file would have to be passed to the ReportServlet (see Figure 7) so the configuration information is loaded at startup and made available to the application. Notice that you could go a step further and instead of generating the interface manually like we did, you could extend the framework so that the interface is generated from the data in the XML configuration file.

This approach should be straightforward -- use Velocity to generate the HTML content of the interface, and then the only steps to add a new type of report are creating the XML configuration file and passing the name of this file to the ReportServlet.


Conclusion

A sample Web application was used as a case study in this article to explain how an application could generate dynamically JasperReports XML templates using the Velocity templating engine. By generating the JasperReports XML template at run time, applications can generate reports that can be tailored based on the user input. To aid in the generation of the XML template, a basic framework was developed. When designing the framework, we made the assumption that reports generated by a particular business organization have a similar structure or layout. This framework could be extracted from the sample application and extended according to your own project's needs.


Download

DescriptionNameSize
Code samplereports-demo.zip  ( HTTP | FTP )2.3 MB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, Rational, DevOps
ArticleID=83922
ArticleTitle=Generating XML templates for JasperReports using Rational Application Developer
publish-date=05252005