Showing posts with label Jasper. Show all posts
Showing posts with label Jasper. Show all posts

Tuesday, January 31, 2012

Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 1)

In this tutorial, we will study how to generate reports from a Spring MVC 3.1 application using JasperReports. We will use jQuery and jqGrid to provide AJAX support and an interactive table. To design the report, we will use iReport, an open source report designer for JasperReports. On the data layer, we'll utilize MySQL, Hibernate, and Spring Data JPA.


Dependencies

  • Spring core 3.1.0.RELEASE
  • Spring Data JPA 1.1.0.M1
  • jQuery 1.6.4
  • jqGrid 4.3.1
  • Jasper Reports 4.5.0
  • See pom.xml for details

Github

To access the source code, please visit the project's Github repository (click here)

Functional Specs

Before we start, let's define our application's specs as follows:
  • Display reports in an interactive table
  • Downloadable reports in PDF and Excel formats
  • Use AJAX to avoid page refresh
  • Users have roles. They are either admin or regular (default)
  • A username is assumed to be unique

Here's our Use Case diagram:
[User]-(View)
[User]-(Download Pdf) 
[User]-(Download Excel)

Database

Our database contains two tables: user and role tables.


user and role table design

User table

The user table contains personal information of each user. Notice the password values are hashed using Md5.

user table

Role table

The role table contains role values of each user. We define a role value of 1 as an admin, while a role value of 2 as a regular user.

role table

Screenshots

Before we start with the actual development, let's preview how our application will look like. This is also a good way to clarify further the application's specs.

Entry page
The entry page is the primary page that users will see. It contains an interactive table where users can view and download records.
Entry page

Pdf document
This is the downloadable Pdf document whenever the user clicks on the Pdf button.
Report in Pdf format as previewed from Chrome

Excel document
This is the downloadable Excel document whenever the user clicks on the Excel button. When using Excel format, the document's appearance varies depending on the application, i.e Microsoft Excel, Google Docs, Open Office.
Report in Excel format as previewed from Google Docs

Progress dialog
When a user clicks on any of the download buttons (Pdf or Excel), a progress dialog will appear and disappears once the download starts.
Progress alert


Next

In the next section, we will discuss the project's structure and write the Java classes. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 1) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 2)

Review

In the previous section, we have laid down the functional specs of the application. In this section, we will discuss the project's structure and write the Java classes in layers.


Project Structure

Our application is a Maven project and therefore follows Maven structure. As we create the classes, we've organized them in logical layers: domain, repository, service, and controller.

Here's a preview of our project's structure:

The Layers

Domain

This layer contains two domain classes, User and Role. They represent our database tables, user and role respectively. Because we're developing a JPA-based repository, both classes must be annotated with JPA annotations.




Data Transfer Object (DTO)

This layer contains four DTO classes:
  • UserDto is a POJO for mapping User objects to and from the presentation layer
  • StatusResponse is a POJO for sending boolean responses to the presentation layer
  • JqgridResponse is a container for UserDto objects for sending records to the jqGrid table. It contains information regarding the number of rows, current page, and total pages.
  • JasperDto is a POJO for mapping User objects into something that Jasper can process easily (This is used for reporting purposes only.)

Note: We won't display the contents of UserDto, StatusReponse, and JqgridResponse here since we've already discussed that on a different tutorial (see Spring MVC 3.1, jqGrid, and Spring Data JPA Integration Guide (Part 2))



Controller

This layer contains two controllers, MediatorController and UserController.
  • MediatorController is responsible for redirecting requests from the root path to the Users page
  • UserController is responsible for handling user related requests, including download requests




Let's describe the methods available from UserController:
  • The records() method returns a list of UserDto objects as JSON strings. This will be displayed on the presentation layer as an interactive grid of data with the help of jqGrid plugin.
  • checkDownloadProgress() method is used to check if a download token is present or not. See Download Token Algorithm below
  • getDownloadToken() method is used to retrieve a download token. See Download Token Algorithm below
  • download() method performs the actual report download by delegating it to the DownloadService.

Download Token Algorithm
(Inspired by Detect when browser receives file download)

This is basically a fancy way of adding a progress box in an AJAX environment, so that we can display the status of a download requests. In a typical AJAX requests, the call is perform asynchronously, and the user does not know if the requests is being processed unless we provide a progress report. Unfortunately, you can't use AJAX directly for file downloads.

However, you can provide an illusion of a progress report through the Download Token Algorithm:
  1. User clicks on download button. A progress box appears.
  2. Download button performs an AJAX requests to retrieve a download token
  3. Once a download token has been received, a call to the actual download URL is performed. (This is not an AJAX requests)
  4. Periodically perform an AJAX requests to check if the download token retrieved earlier still exists. If not, the download has started.
  5. If the download token does not exists anymore, close the progress box.

Repository

This layer contains a single interface, UserRepository. This is our data access object (DAO). With the help of Spring Data JPA, Spring will automatically provide the actual implementation.

What is Spring Data JPA?

Spring JPA is part of the umbrella Spring Data project that makes it easy to easily implement JPA based repositories.

Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that's actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.

Source: http://www.springsource.org/spring-data/jpa



Service

This layer contains the following services:
  • TokenService: This is responsible for providing and managing download tokens. See the Download Token Algorithm above.
  • JasperDatasourceService: Wraps a list of objects into a JRBeanCollectionDataSource. This is the datasource for our Jasper reports.
  • ExporterService: This is responsible for exporting a Jasper report into different formats, such as Pdf and Excel.
  • DownloadService: This is responsible for the actual download requests and writing of reports to the output stream






The DownloadService is the heart of the download process. The algorithm is as follows:
  1. Add report parameters
  2. Retrieve template
  3. Convert template to JasperDesign
  4. Compile design to JasperReport
  5. Create the JasperPrint object
  6. Create an output byte stream where data will be written
  7. Export report
  8. Write to reponse stream

Utilities

  • JqgridFilter is a Java representation of a jqGrid filter
  • JqgridObjectMapper is used to convert a jqGrid filter to a JqgridFilter object
  • UserMapper is used to map User objects to UserDto objects
  • TraceInterceptor is an AOP-based utility class to help us debug our application. This is a subclass of CustomizableTraceInterceptor. See Spring Data JPA FAQ

Note: We won't display the contents of JqgridFilter, JqgridObjectMapper, UserMapper, and TraceInterceptor here since we've already discussed them on a different tutorial. See Spring Data JPA FAQ

Next

In the next section, we will create the Jasper report layout and design it using iReport. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 2) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 3)

Review

In the previous section, we have created the Java classes and discussed the download process. In this section, we will focus on the Jasper layout and study how to design one using iReport.


Jasper Layout

The Jasper report layout is basically an XML document. You can create it through any text editor (if you know the syntax) or you can use a WYSWYG-editor. We choose the latter because it's easier.

What is iReport?

iReport is the free, open source report designer for JasperReports. Create very sophisticated layouts containing charts, images, subreports, crosstabs and much more. Access your data through JDBC, TableModels, JavaBeans, XML, Hibernate, CSV, and custom sources. Then publish your reports as PDF, RTF, XML, XLS, CSV, HTML, XHTML, text, DOCX, or OpenOffice.

Source: iReport

Final Design

Before we proceed, let's preview the final design:

Admittedly, this isn't the best looking design, but it's good enough for our purpose.


Project Structure

Below is a screenshot of the project's structure:

Installation

To start using iReport, we must first download and install one. Below are the instructions for installing and running iReport in Windows (Ubuntu will follow later):

Windows
1. Open a browser and visit the iReport site at http://jasperforge.org/projects/ireport

2. Click on the download button. It will ask you to register first, but you can skip it.
You should see the following options:

3. Choose the Windows installer.

4. Once the download is finished, install it.

Designing

We'll now start designing our report

1. Run iReport

2. Go to File and click New. You should see the following window

3. Select Report and click on the Coffee Landscape template. You will be asked to save the project.

4. Click Finish when done. You should see the following window:

Now, it's time to design the report.

5. On the left side, under the Report Inspector, you should see a list of items. Expand the Fields section. You should see the following items: id, username, firstName, lastName, role.

Click on any of the fields and change their properties as indicated below (the Properties panel is located on the right-side):






6. On the left side, under the Report Inspector, expand the Detail section. You should see the following items: $F{id}, $F{username}, $F{firstName}, $F{lastName}, $F{role}. These are value placeholders.

Click on any of the fields and change their properties as indicated below. Make sure to edit the Detail section itself and the bounding box as well.









7. On the left side, under the Report Inspector, expand the Column Header section. You should see the following items: ID, Username, First Name, LastName, Role. These are the labels for each fields accordingly.

Click on any of the fields and change their properties as indicated below. Make sure to edit the Detail section itself and the bounding box as well.






8. Now, let's update the title. Change it from Coffee Title to User Records


9. Update the subtitle as well. Change it from Coffee SubTitle to A Summary:

10. We're done with the design. Now, let's update the report's programming language (This is critical!). Click outside the report layout. Then on the Properties panel, scroll down and look for the Language property. Change it from Groovy to Java!

Before
After
11. Save the file, and we're done.

Transferring the Layout

After creating the design, we should now copy and save it to the classpath directory of our Spring project.

Here are the steps:

1. Open the folder or directory where you saved your report. Notice that the images are saved along with the report.
  • coffee.jpg: an image
  • coffee_stain.jpg: an image
  • users.jasper: compiled report (we don't need this one)
  • users.jrxml: the report layout (this is important)

2. Copy all the files except the users.jasper and paste it under the src/main/resources folder of your Spring project.

That's it! We've completed the transfer.

Next

In the next section, we will focus on the presentation layer, in particular the tabular display of our records using jqGrid and HTML. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 3) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 4)

Review

In the previous section, we have created the Jasper report layout using iReport and learned where to place them on our Spring project. In this section, we will focus on the presentation layer and learn how to display data using HTML and jqGrid.


Presentation Layer

We will use jqGrid to display our data in an interactive table and jQuery to provide AJAX support.

What is jQuery?

jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.

Source: http://jquery.com/

What is jqGrid?

jqGrid is an Ajax-enabled JavaScript control that provides solutions for representing and manipulating tabular data on the web. Since the grid is a client-side solution loading data dynamically through Ajax callbacks, it can be integrated with any server-side technology, including PHP, ASP, Java Servlets, JSP, ColdFusion, and Perl.

Source: http://www.trirand.com/jqgridwiki/doku.php

Preview

We only have a single HTML file (users.jsp, a JSP to be exact) to perform all actions. This page contains our jqGrid table and buttons for downloading reports in Pdf and Excel format.

Entry page

Source

At first glance, when you look at the JavaScript code, it is somewhat intimidating, but once you've familiarized with the jqGrid syntax, you'll find that it's actually simple. If you aren't familiary with jqGrid, please visit the jqGrid Official Wiki


You might be asking: "What's that humongous lines of jibberish code?" If you'd been following my blog, you would notice that I have tackled jqGrid a couple of times from my previous tutorials. If my explanation in this tutorial isn't enough, please see the following tutorials for an alternative perspective:


An In-depth Look

If we partition this JSP page, you will notice the following sections:
  • URL imports
  • JavaScript and CSS imports
  • jqGrid initialization
  • JavaScript functions: downloadXls(), downloadPdf(), download()

    Pay attention to the download() function's algorithm:

    1. Retrieve a download token via AJAX.
    2. Store the token.
    3. Show download progress dialog box.
    4. Start actual download.
    5. Do a periodic AJAX call to verify if token still exists. If not, hide download progress box.
  • HTML table

Notice how we've separated the HTML markup from the JavaScript code. We could, of course, move that one huge JavaScript code in an external js file, and make the HTML look somewhat smaller. But I'll leave that exercise to my readers.

Next

In the next section, we will focus on the configuration files for enabling Spring MVC. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 4) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 6)

Review

We have just completed our application! In the previous sections, we have discussed the functional specs, created the Java classes, designed the Jasper reporty layout, declared the configuration files, and wrote the JSP files. In this section, we will build and run the application using Maven, and show how to import the project in Eclipse.


Running the Application

Access the source code

To download the source code, please visit the project's Github repository (click here)

Preparing the data source

  1. Run MySQL (install one if you don't have one yet)
  2. Create a new database:
    spring_jasper_tutorial
  3. Import the following file which is included in the source code under the src/main/resources folder:
    spring_jasper_tutorial.sql

Building with Maven

  1. Ensure Maven is installed
  2. Open a command window (Windows) or a terminal (Linux/Mac)
  3. Run the following command:
    mvn tomcat:run
  4. You should see the following output:
    [INFO] Scanning for projects...
    [INFO] Searching repository for plugin with prefix: 'tomcat'.
    [INFO] artifact org.codehaus.mojo:tomcat-maven-plugin: checking for updates from central
    [INFO] artifact org.codehaus.mojo:tomcat-maven-plugin: checking for updates from snapshots
    [INFO] ------------------------------------------
    [INFO] Building spring-jasper-tutorial Maven Webapp
    [INFO]    task-segment: [tomcat:run]
    [INFO] ------------------------------------------
    [INFO] Preparing tomcat:run
    [INFO] [apt:process {execution: default}]
    [INFO] [resources:resources {execution: default-resources}]
    [INFO] [tomcat:run {execution: default-cli}]
    [INFO] Running war on http://localhost:8080/spring-jasper-tutorial
    Jan 31, 2012 10:39:22 PM org.apache.catalina.startup.Embedded start
    INFO: Starting tomcat server
    Jan 31, 2012 10:39:23 PM org.apache.catalina.core.StandardEngine start
    INFO: Starting Servlet Engine: Apache Tomcat/6.0.29
    Jan 31, 2012 10:39:23 PM org.apache.catalina.core.ApplicationContext log
    INFO: Initializing Spring root WebApplicationContext
    Jan 31, 2012 10:39:29 PM org.apache.coyote.http11.Http11Protocol init
    INFO: Initializing Coyote HTTP/1.1 on http-8080
    
  5. Note: If the project will not build due to missing repositories, please enable the repositories section in the pom.xml!

Access the Entry page

  1. Follow the steps with Building with Maven
  2. Open a browser
  3. Enter the following URL (8080 is the default port for Tomcat):
    http://localhost:8080/spring-jasper-tutorial/

Import the project in Eclipse

  1. Ensure Maven is installed
  2. Open a command window (Windows) or a terminal (Linux/Mac)
  3. Run the following command:
    mvn eclipse:eclipse -Dwtpversion=2.0
  4. You should see the following output:
    [INFO] Scanning for projects...
    [INFO] Searching repository for plugin with prefix: 'eclipse'.
    [INFO] org.apache.maven.plugins: checking for updates from central
    [INFO] org.apache.maven.plugins: checking for updates from snapshots
    [INFO] org.codehaus.mojo: checking for updates from central
    [INFO] org.codehaus.mojo: checking for updates from snapshots
    [INFO] artifact org.apache.maven.plugins:maven-eclipse-plugin: checking for updates from central
    [INFO] artifact org.apache.maven.plugins:maven-eclipse-plugin: checking for updates from snapshots
    [INFO] -----------------------------------------
    [INFO] Building spring-jasper-tutorial Maven Webapp
    [INFO]    task-segment: [eclipse:eclipse]
    [INFO] -----------------------------------------
    [INFO] Preparing eclipse:eclipse
    [INFO] No goals needed for project - skipping
    [INFO] [eclipse:eclipse {execution: default-cli}]
    [INFO] Adding support for WTP version 2.0.
    [INFO] -----------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] -----------------------------------------
    
    This command will add the following files to your project:
    .classpath
    .project
    .settings
    target
    You may have to enable "show hidden files" in your file explorer to view them
  5. Open Eclipse and import the project

Conclusion

That's it! We've have successfully completed our Spring MVC 3.1 web application. We've learned how to add downloadable reports with JasperReports in an AJAX application, and use iReport to design the layout.

I hope you've enjoyed this tutorial. Don't forget to check my other tutorials at the Tutorials section.

Revision History


Revision Date Description
1 Jan 31 2012 Uploaded tutorial and Github repository

StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 6) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 5)

Review

In the previous section, we have focused on the presentation layer and discussed jqGrid. In this section, we will focus on the configuration files, in particular the XML files.


Configuration

Database properties

We'll use MySQL as our application's database. However, it's also possible to use a different database provider. Therefore, to make switching database simpler, we've externalized our database configuration within a properties file.


Application Context

Below is a typical application context file for enabling Spring MVC support.


Spring Data

In conjuction with the spring.properties file, we have to declare the actual datasource. Notice we're using JPA and Spring Data JPA support.


Next

In the next section, we will build and run the application using Maven, and show how to import the project in Eclipse. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 5) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Wednesday, February 9, 2011

Spring 3 - Apache POI - Hibernate: Creating an Excel Report Tutorial

In this tutorial we will build a simple Spring MVC 3 application with reporting capabilities. We will use Apache POI to generate dynamic Excel reports and Hibernate for the ORM framework. We will use a simple Java List as our data source. The data will be retrieved from a MySQL database.

This tutorial is part of the following reporting tutorial series that uses Jasper, DynamicJasper, and Apache POI:

Spring 3 - Apache POI - Hibernate: Creating an Excel Report Tutorial
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport
Spring 3 - DynamicJasper - Hibernate Tutorial: Using Plain List
Spring 3 - DynamicJasper - Hibernate Tutorial: Using JRDataSource
Spring 3 - DynamicJasper - Hibernate Tutorial: Using HQL Query

All of these tutorials produce the same document, and all of them demonstrate different ways of creating the same report.

What is Apache POI?
The Apache POI Project's mission is to create and maintain Java APIs for manipulating various file formats based upon the Office Open XML standards (OOXML) and Microsoft's OLE 2 Compound Document format (OLE2). In short, you can read and write MS Excel files using Java. In addition, you can read and write MS Word and MS PowerPoint files using Java. Apache POI is your Java Excel solution (for Excel 97-2008).

Source: http://poi.apache.org/

Background

Before we start our application, let's preview first the final print document:

Our document is a simple Excel document. It's a Sales Report for a list of power supplies. The data is retrieved from a MySQL database.

Domain

Notice that for each Power Supply entry there's a common set of properties:
id
brand
model
maximum power
price
efficiency

Development

Domain

We'll start our application by declaring the domain object PowerSupply

PowerSupply.java
package org.krams.tutorial.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * A simple POJO containing the common properties of a Power Supply
 * This is an annotated Hibernate entity. 
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Entity
@Table(name = "POWER_SUPPLY")
public class PowerSupply implements Serializable {

 private static final long serialVersionUID = 8634209606034270882L;

 @Id
 @Column(name = "ID")
 @GeneratedValue
 private Long id;
 
 @Column(name = "BRAND")
 private String brand;
 
 @Column(name = "MODEL")
 private String model;
 
 @Column(name = "MAXIMUM_POWER")
 private String maximumPower;
 
 @Column(name = "PRICE")
 private Double price;
 
 @Column(name = "EFFICIENCY")
 private Double efficiency;

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getBrand() {
  return brand;
 }

 public void setBrand(String brand) {
  this.brand = brand;
 }

 public String getModel() {
  return model;
 }

 public void setModel(String model) {
  this.model = model;
 }

 public String getMaximumPower() {
  return maximumPower;
 }

 public void setMaximumPower(String maximumPower) {
  this.maximumPower = maximumPower;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public Double getEfficiency() {
  return efficiency;
 }

 public void setEfficiency(Double efficiency) {
  this.efficiency = efficiency;
 }
 
}
PowerSupply is a simple POJO containing six private fields. Each of these fields have been annotated with @Column and assigned with corresponding database column names.
ID
BRAND
MODEL
MAXIMUM_POWER
PRICE
EFFICIENCY

Service

We'll be declaring a single service named DownloadService. This service is the heart of the application that will process and retrieve the report document.

The service will run the following steps:
1. Create new workbook
2. Create new worksheet
3. Define starting indices for rows and columns
4. Build layout 
5. Fill report
6. Set the HttpServletResponse properties
7. Write to the output stream

DownloadService.java
package org.krams.tutorial.service;

import java.util.List;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.krams.tutorial.domain.PowerSupply;
import org.krams.tutorial.report.FillManager;
import org.krams.tutorial.report.Layouter;
import org.krams.tutorial.report.Writer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * Service for processing Apache POI-based reports
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Service("downloadService")
@Transactional
public class DownloadService {

 private static Logger logger = Logger.getLogger("service");
 
 @Resource(name="sessionFactory")
 private SessionFactory sessionFactory;
 
 /**
  * Processes the download for Excel format.
  * It does the following steps:
  * <pre>1. Create new workbook
  * 2. Create new worksheet
  * 3. Define starting indices for rows and columns
  * 4. Build layout 
  * 5. Fill report
  * 6. Set the HttpServletResponse properties
  * 7. Write to the output stream
  * </pre>
  */
 @SuppressWarnings("unchecked")
 public void downloadXLS(HttpServletResponse response) throws ClassNotFoundException {
  logger.debug("Downloading Excel report");
  
  // 1. Create new workbook
  HSSFWorkbook workbook = new HSSFWorkbook();
  
  // 2. Create new worksheet
  HSSFSheet worksheet = workbook.createSheet("POI Worksheet");
  
  // 3. Define starting indices for rows and columns
  int startRowIndex = 0;
  int startColIndex = 0;
  
  // 4. Build layout 
  // Build title, date, and column headers
  Layouter.buildReport(worksheet, startRowIndex, startColIndex);

  // 5. Fill report
  FillManager.fillReport(worksheet, startRowIndex, startColIndex, getDatasource());
  
  // 6. Set the response properties
  String fileName = "SalesReport.xls";
  response.setHeader("Content-Disposition", "inline; filename=" + fileName);
  // Make sure to set the correct content type
  response.setContentType("application/vnd.ms-excel");
  
  //7. Write to the output stream
  Writer.write(response, worksheet);
 }
 
 /**
  * Retrieves the datasource as as simple Java List.
  * The datasource is retrieved from a Hibernate HQL query.
  */
 @SuppressWarnings("unchecked")
 private List<PowerSupply> getDatasource() {
  
      // Retrieve session
  Session session = sessionFactory.getCurrentSession();
  // Create query for retrieving products
  Query query = session.createQuery("FROM PowerSupply");
  // Execute query
  List<PowerSupply> result = query.list();
  
  // Return the datasource
  return result;
 }
}
This service is our download service for generating the report document. It should be clear what each line of code is doing.

The service has been divided into separate classes to encapsulate specific jobs.

The Layouter

The purpose of the Layouter is to layout the design of the report. Here's where we declare the dynamic columns and special properties of the document.

Layouter.java
package org.krams.tutorial.report;

import java.util.Date;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.util.CellRangeAddress;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;

/**
 * Builds the report layout, the template, the design, the pattern or whatever synonym you may want to call it.
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Layouter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Builds the report layout. 
  * <p>
  * This doesn't have any data yet. This is your template.
  */
 public static void buildReport(HSSFSheet worksheet, int startRowIndex, int startColIndex) {
  // Set column widths
  worksheet.setColumnWidth(0, 5000);
  worksheet.setColumnWidth(1, 5000);
  worksheet.setColumnWidth(2, 5000);
  worksheet.setColumnWidth(3, 5000);
  worksheet.setColumnWidth(4, 5000);
  worksheet.setColumnWidth(5, 5000);
  
  // Build the title and date headers
  buildTitle(worksheet, startRowIndex, startColIndex);
  // Build the column headers
  buildHeaders(worksheet, startRowIndex, startColIndex);
 }
 
 /**
  * Builds the report title and the date header
  * 
  * @param worksheet
  * @param startRowIndex starting row offset
  * @param startColIndex starting column offset
  */
 public static void buildTitle(HSSFSheet worksheet, int startRowIndex, int startColIndex) {
  // Create font style for the report title
  Font fontTitle = worksheet.getWorkbook().createFont();
  fontTitle.setBoldweight(Font.BOLDWEIGHT_BOLD);
  fontTitle.setFontHeight((short) 280);
  
        // Create cell style for the report title
        HSSFCellStyle cellStyleTitle = worksheet.getWorkbook().createCellStyle();
        cellStyleTitle.setAlignment(CellStyle.ALIGN_CENTER);
        cellStyleTitle.setWrapText(true);
        cellStyleTitle.setFont(fontTitle);
  
        // Create report title
  HSSFRow rowTitle = worksheet.createRow((short) startRowIndex);
  rowTitle.setHeight((short) 500);
  HSSFCell cellTitle = rowTitle.createCell(startColIndex);
  cellTitle.setCellValue("Sales Report");
  cellTitle.setCellStyle(cellStyleTitle);
  
  // Create merged region for the report title
  worksheet.addMergedRegion(new CellRangeAddress(0,0,0,5));
  
  // Create date header
  HSSFRow dateTitle = worksheet.createRow((short) startRowIndex +1);
  HSSFCell cellDate = dateTitle.createCell(startColIndex);
  cellDate.setCellValue("This report was generated at " + new Date());
 }
 
 /**
  * Builds the column headers
  * 
  * @param worksheet
  * @param startRowIndex starting row offset
  * @param startColIndex starting column offset
  */
 public static void buildHeaders(HSSFSheet worksheet, int startRowIndex, int startColIndex) {
  // Create font style for the headers
  Font font = worksheet.getWorkbook().createFont();
        font.setBoldweight(Font.BOLDWEIGHT_BOLD);

        // Create cell style for the headers
  HSSFCellStyle headerCellStyle = worksheet.getWorkbook().createCellStyle();
  headerCellStyle.setFillBackgroundColor(HSSFColor.GREY_25_PERCENT.index);
  headerCellStyle.setFillPattern(CellStyle.FINE_DOTS);
  headerCellStyle.setAlignment(CellStyle.ALIGN_CENTER);
  headerCellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
  headerCellStyle.setWrapText(true);
  headerCellStyle.setFont(font);
  headerCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
  
  // Create the column headers
  HSSFRow rowHeader = worksheet.createRow((short) startRowIndex +2);
  rowHeader.setHeight((short) 500);
  
  HSSFCell cell1 = rowHeader.createCell(startColIndex+0);
  cell1.setCellValue("Id");
  cell1.setCellStyle(headerCellStyle);

  HSSFCell cell2 = rowHeader.createCell(startColIndex+1);
  cell2.setCellValue("Brand");
  cell2.setCellStyle(headerCellStyle);

  HSSFCell cell3 = rowHeader.createCell(startColIndex+2);
  cell3.setCellValue("Model");
  cell3.setCellStyle(headerCellStyle);

  HSSFCell cell4 = rowHeader.createCell(startColIndex+3);
  cell4.setCellValue("Max Power");
  cell4.setCellStyle(headerCellStyle);

  HSSFCell cell5 = rowHeader.createCell(startColIndex+4);
  cell5.setCellValue("Price");
  cell5.setCellStyle(headerCellStyle);
 
  HSSFCell cell6 = rowHeader.createCell(startColIndex+5);
  cell6.setCellValue("Efficiency");
  cell6.setCellStyle(headerCellStyle);
 }
}

The FillManager

The purpose of the FillManager is to fill the Excel report with data from the data source.

FillManager.java
package org.krams.tutorial.report;

import java.util.List;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ss.usermodel.CellStyle;
import org.krams.tutorial.domain.PowerSupply;

public class FillManager {

 /**
  * Fills the report with content
  * 
  * @param worksheet
  * @param startRowIndex starting row offset
  * @param startColIndex starting column offset
  * @param datasource the data source
  */
 public static void fillReport(HSSFSheet worksheet, int startRowIndex, int startColIndex, List<PowerSupply> datasource) {
  // Row offset
  startRowIndex += 2;
  
  // Create cell style for the body
  HSSFCellStyle bodyCellStyle = worksheet.getWorkbook().createCellStyle();
  bodyCellStyle.setAlignment(CellStyle.ALIGN_CENTER);
  bodyCellStyle.setWrapText(true);
  
  // Create body
  for (int i=startRowIndex; i+startRowIndex-2< datasource.size()+2; i++) {
   // Create a new row
   HSSFRow row = worksheet.createRow((short) i+1);

   // Retrieve the id value
   HSSFCell cell1 = row.createCell(startColIndex+0);
   cell1.setCellValue(datasource.get(i-2).getId());
   cell1.setCellStyle(bodyCellStyle);

   // Retrieve the brand value
   HSSFCell cell2 = row.createCell(startColIndex+1);
   cell2.setCellValue(datasource.get(i-2).getBrand());
   cell2.setCellStyle(bodyCellStyle);

   // Retrieve the model value
   HSSFCell cell3 = row.createCell(startColIndex+2);
   cell3.setCellValue(datasource.get(i-2).getModel());
   cell3.setCellStyle(bodyCellStyle);

   // Retrieve the maximum power value
   HSSFCell cell4 = row.createCell(startColIndex+3);
   cell4.setCellValue(datasource.get(i-2).getMaximumPower());
   cell4.setCellStyle(bodyCellStyle);

   // Retrieve the price value
   HSSFCell cell5 = row.createCell(startColIndex+4);
   cell5.setCellValue(datasource.get(i-2).getPrice());
   cell5.setCellStyle(bodyCellStyle);
  
   // Retrieve the efficiency value
   HSSFCell cell6 = row.createCell(startColIndex+5);
   cell6.setCellValue(datasource.get(i-2).getEfficiency());
   cell6.setCellStyle(bodyCellStyle);
  }
 }
}

The Writer

The purpose of the Writer is to write the "exported" worksheet to the output stream. Once the document has been written to the stream, the user will receive the document ready to be downloaded.

Writer.java
package org.krams.tutorial.report;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFSheet;

/**
 * Writes the report to the output stream
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Writer {

 private static Logger logger = Logger.getLogger("service");
 /**
  * Writes the report to the output stream
  */
 public static void write(HttpServletResponse response, HSSFSheet worksheet) {
  
  logger.debug("Writing report to the stream");
  try {
   // Retrieve the output stream
   ServletOutputStream outputStream = response.getOutputStream();
   // Write to the output stream
   worksheet.getWorkbook().write(outputStream);
   // Flush the stream
   outputStream.flush();

  } catch (Exception e) {
   logger.error("Unable to write report to the output stream");
  }
 }
}

Controller

We've completed the domain and service layer of the application. Since we're developing a Spring MVC web application, we're required to declare a controller that will handle the user's request.

DownloadController.java
package org.krams.tutorial.controller;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.krams.tutorial.service.DownloadService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles download requests
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Controller
@RequestMapping("/download")
public class DownloadController {

 private static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="downloadService")
 private DownloadService downloadService;

 /**
  * Downloads the report as an Excel format. 
  * <p>
  * Make sure this method doesn't return any model. Otherwise, you'll get 
  * an "IllegalStateException: getOutputStream() has already been called for this response"
  */
    @RequestMapping(value = "/xls", method = RequestMethod.GET)
    public void getXLS(HttpServletResponse response, Model model) throws ClassNotFoundException {
     logger.debug("Received request to download report as an XLS");
     
     // Delegate to downloadService. Make sure to pass an instance of HttpServletResponse 
     downloadService.downloadXLS(response);
 }
}
DownloadController is a simple controller that handles download requests. It delegates report generation to the DownloadService. Notice we're required to pass the HttpServletResponse to the service.

Database Configuration

We've completed the MVC module of the application. However we haven't created yet the Hibernate configuration and the MySQL database.

Our first task is to create an empty MySQL database.

Here are the steps:
1. Run MySQL
2. Open MySQL admin
3. Create a new database mydb

In this tutorial I've setup a local MySQL database and used phpmyadmin to administer it.

Next, we'll be declaring a hibernate-context.xml configuration file. Its purpose is to contain all of Spring-related configuration for Hibernate.

hibernate-context.xml

This configuration requires two external configurations further:

spring.properties
# database properties
app.jdbc.driverClassName=com.mysql.jdbc.Driver
app.jdbc.url=jdbc:mysql://localhost/mydb
app.jdbc.username=root
app.jdbc.password=

#hibernate properties
hibernate.config=/WEB-INF/hibernate.cfg.xml

hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  
<hibernate-configuration>
 <session-factory>
  <!-- We're using a MySQL database so the dialect needs to be MySQL as well -->
  <!-- Also we want to use MySQL's InnoDB engine -->
  <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
  
  <!-- Enable this to see the Hibernate generated SQL statements in the logs -->
  <property name="show_sql">false</property>
  
  <!-- Setting this to 'create' will drop our existing database and re-create a new one.
    This is only good for testing. In production, this is a bad idea! -->
  <property name="hbm2ddl.auto">create</property>
 </session-factory>
</hibernate-configuration>

The Import.SQL

After declaring all the Hibernate-related configuration, let's now declare a SQL script that will populate our database with a sample data automatically.

import.sql
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Corsair', 'CMPSU-750TX', '750W', '109.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Antec', 'NEO ECO 620C', '620W', '69.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'OCZ', 'OCZ700MXSP', '700W', '89.99', '0.86')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Thermaltake', 'W0070RUC', '430W', '43.99', '0.65')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'COOLER MASTER', 'RS-460-PSAR-J3', '460W', '29.99', '0.70')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Rosewill', 'RG530-S12', '530W', '54.99', '0.80')
Make sure to place this document under the classpath. Hibernate will automatically import the contents of this document everytime your start the application. This is dictated by the hbm2ddl.auto setting we declared in the hibernate.cfg.xml earlier.

We're not required to create this import.sql file. We could of course create a MySQL SQL script and import it directly to the database, or add the data manually in the database. I just believe this is convenient for development purposes.

Spring MVC Configuration

We've declared all the necessary classes and Hibernate-related configuration of the application. However, we haven't created yet the required Spring MVC configuration.

Let's begin with the web.xml
web.xml
<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/krams/*</url-pattern>
 </servlet-mapping>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/krams
In the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml.

spring-servlet.xml
<!-- Declare a view resolver for resolving JSPs -->
 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
      p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
By convention, we must declare an applicationContext.xml as well.

applicationContext.xml
<!-- Activates various annotations to be detected in bean classes -->
 <context:annotation-config />
 
 <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
  For example @Controller and @Service. Make sure to set the correct base-package-->
 <context:component-scan base-package="org.krams.tutorial" />
 
 <!-- Configures the annotation-driven Spring MVC Controller programming model.
 Note that, with Spring 3.0, this tag works in Servlet MVC only!  -->
 <mvc:annotation-driven /> 
 
 <!-- Loads Hibernate related configuration -->
 <import resource="hibernate-context.xml" />

Run the Application

We've completed the application. Our last task is to run the application and download the report.

To run the application, open your browser and enter the following URL:
http://localhost:8080/spring-poi-hibernate/krams/download/xls
This will automatically download the report document. Again, here's the final screenshot of the document:

Conclusion

That's it. We've managed to build a simple Spring MVC 3 application with reporting capabilities. We used Apache POI to generate the dynamic Excel reports and Hibernate for the ORM framework. Lastly, we used a plain Java List as the data source where the data is retrieved from a MySQL database.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-poi-integration-tutorial/

You can download the project as a Maven build. Look for the spring-poi-hibernate.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3 - Apache POI - Hibernate: Creating an Excel Report Tutorial ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share