Sunday, January 3, 2010

Configure Jasperreports 3.x Spring 3.x Maven 2.x by Roo 1.x

In this post we are going to see how easy and fast we could integrate Jasperreports to the Spring-MVC based projects by Spring-Roo. Spring Roo is a next-generation rapid application development tool for Java developers.
Note: Spring Roo uses Maven2 as a default build tool for generated projects.


In the first step we will create sample project with Spring-Roo and in the second step we will add required configurations manually.
Before going to steps, have a look at my test environment :
jasperreports-3.5.2
springframework-3.0.0.RELEASE
spring-roo-1.0.0.RELEASE
apache-maven-2.2.1
sun-jdk1.6.0_16 (64bit)
linux-ubuntu-8.04 (64bit)

And don't worry about the differences to yours , you just need to have Spring-Roo-1.0.0.RELEASE installed, then you can expect same result as me.

Step one: create sample project with Spring Roo
$mkdir sample
$cd sample
$roo
roo> project --topLevelPackage com.company.sample
roo> persistence setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT
roo> entity --class ~.domain.Customer
roo> field string --fieldName name --class ~.domain.Customer
roo> controller scaffold --class ~.web.CustomerController --entity ~.domain.Customer 
roo> quit
$mvn clean tomcat:run


Now you can go to http://localhost:8080/sample/ and check the CRUD functionality that you have created with 'controller scaffold' add-ons.

Step two: modify files manually:
You may want to edit files in Eclipse IDE or STS :
Note: STS has all required plugins installed.
$cd sample
$mvn eclipse:eclipse

sample/pom.xml
      <dependencies>
...
        <dependency>
            <groupid>org.springframework</groupId>
            <artifactId>org.springframework.context.support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>jasperreports</groupId>
            <artifactId>jasperreports</artifactId>
            <version>3.5.3</version>
        </dependency>
      </dependencies>

You may ask why jasperreports version 3.5.3? while last stable version is 3.7.0? the answer is I couldn't find any higher version in mvnrepository.com . If you could find a higher version in any other Maven2 repositories, please let me know.

sample/src/main/webapp/WEB-INF/spring/webmvc-config.xml

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


sample/src/main/webapp/WEB-INF/classes/views.properties
customerReportList.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
customerReportList.url=/WEB-INF/reports/customerReportList.jasper
customerReportList.reportDataKey=customerReportList

sample/src/main/webapp/WEB-INF/reports/customerReportList.jrxml
<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="customerList" pageWidth="612" pageHeight="792" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
  <field name="name" class="java.lang.String"/>
  <columnHeader>
    <band height="31" splitType="Stretch">
      <staticText>
        <reportElement x="0" y="0" width="100" height="20"/>
        <text><![CDATA[NAME]]></text>
      </staticText>
    </band>
  </columnHeader>
  <detail>
    <band height="24" splitType="Stretch">
      <textField>
        <reportElement x="0" y="0" width="100" height="20"/>
        <textElement/>
        <textFieldExpression class="java.lang.String"><![CDATA[$F{name}]]></textFieldExpression>
      </textField>
    </band>
  </detail>
</jasperReport>


sample/src/main/webapp/WEB-INF/reports/customerReportList.jasper
You have different options to compile this file, in my case jasperreports-maven-plugin from mojo.codehaus.org
sample/pom.xml
...
  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jasperreports-maven-plugin</artifactId>
    <configuration>
      <sourceDirectory>src/main/webapp/WEB-INF/reports</sourceDirectory>
      <outputDirectory>src/main/webapp/WEB-INF/reports</outputDirectory>
    </configuration>
    <executions>
      <execution>
        <goals>
          <goal>compile-reports</goal>
        </goals>
      </execution>
    </executions>
    <dependencies>
      <dependency>
        <groupId>jasperreports</groupId>
        <artifactId>jasperreports</artifactId>
        <version>3.5.3</version>
      </dependency>
      <dependency>
        <groupId>org.apache.log4j</groupId>
        <artifactId>com.springsource.org.apache.log4j</artifactId>
        <version>1.2.15</version>
      </dependency>                    
     </dependencies>
  </plugin>
</plugins>
</build>


sample/src/main/java/com/company/sample/web/CustomerController.java
package com.company.sample.web;

import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

import org.springframework.roo.addon.web.mvc.controller.RooWebScaffold;
import com.company.sample.domain.Customer;

import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.stereotype.Controller;

@RooWebScaffold(path = "customer", automaticallyMaintainView = true, formBackingObject = Customer.class)
@RequestMapping("/customer/**")
@Controller
public class CustomerController {

 @RequestMapping(value ="/customer/report/pdf", method = RequestMethod.GET)
 public String fireReport(ModelMap modelMap) {
  JRBeanCollectionDataSource jrDataSource = new JRBeanCollectionDataSource(Customer.findAllCustomers(),false);
  modelMap.put("customerReportList", jrDataSource);
  return "customerReportList";
 }
}

That's all, add some customers and check 'http://localhost:8080/sample/customer/report/pdf' .

What else?
I also tried to get multiple report format + changing report name at runtime:
sample/src/main/webapp/WEB-INF/spring/webmvc-config.xml
<beans ...>
...
  <bean id="jasperReportsMultiFormatView" name="jasperReportsMultiFormatViewBean"
class="com.company.sample.web.report.CustomJasperReportsMultiFormatView">

    <!-- The value _rep_name_ will be replaced with the report name -->
    <property name="contentDispositionMappings">
      <props>
        <prop key="html">attachment; filename=_rep_name_.html</prop>
        <prop key="pdf">attachment; filename=_rep_name_.pdf</prop>
        <prop key="xls">attachment; filename=_rep_name_.xls</prop>
        <prop key="csv">attachment; filename=_rep_name_.csv</prop>
      </props>
    </property>
  </bean>
</beans>

sample/src/main/webapp/WEB-INF/classes/views.properties
customerReportList.(class)=com.company.sample.web.report.CustomJasperReportsMultiFormatView

sample/src/main/java/com/company/sample/web/report/CustomJasperReportsMultiFormatView.java
package com.company.sample.web.report;

import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JasperPrint;
import org.springframework.web.servlet.view.jasperreports.JasperReportsMultiFormatView;

/**
 * This calss override renderReport() method for replace report name .
 */
public class CustomJasperReportsMultiFormatView extends JasperReportsMultiFormatView {

 protected void renderReport(JasperPrint populatedReport, Map model, HttpServletResponse response) throws Exception {
        super.renderReport(populatedReport,model,response);
        
        // replace content disposition header filename with the report names.
        Properties contentDispositions = this.getContentDispositionMappings();
        
        Enumeration enumContDispKeys = contentDispositions.keys();
        // iterate over all disposition mappings and replace the word _rep_name_ with the reportName
        while(enumContDispKeys.hasMoreElements()){
            Object contDispKey = enumContDispKeys.nextElement();
            // check whether string before cast.
            if(contDispKey instanceof String){
                // get the disposition string
                String dispositionStr = contentDispositions.getProperty((String)contDispKey);
                // set the new value in the properties
                contentDispositions.setProperty((String)contDispKey,dispositionStr.replace("_rep_name_",populatedReport.getName()));
            }
        }
    }
}

I found the above code from this post . But I got no success to change report name.I also moved super.renderReport(..) to the bottom of method but there was no difference. Maybe it is not working because of my jasperreports version, If you find the problem please let me know, thanks.

And finally sample/src/main/java/com/company/sample/web/CustomerController.java
 @RequestMapping(value ="/customer/report/{format}", method = RequestMethod.GET)
 public String fireReport(ModelMap modelMap, @PathVariable("format") String format) {
  JRBeanCollectionDataSource jrDataSource = new JRBeanCollectionDataSource(Customer.findAllCustomers(),false);
  modelMap.put("customerReportList", jrDataSource);
  modelMap.put("format", format);
  return "customerReportList";
 }

Now you could see different formats like this:
http://localhost:8080/sample/customer/report/pdf
http://localhost:8080/sample/customer/report/csv
http://localhost:8080/sample/customer/report/html
http://localhost:8080/sample/customer/report/xls

Note: for xls format we need to add below dependency to pom.xml
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>3.2-FINAL</version>
</dependency>


What last?
Spring-Roo is awesome ;-) give it a try and join it's community. I really had a good time with Roo in my recent project. Actually reading Roo's forum and Roo's twittes are my favorite reads now! :-)
I could feel Roo's potential, for example look at this post and what we have done in step two , All of these could be done with Roo add-ons in the near future. If you want to make that happen sooner please vote for this issue : https://jira.springsource.org/browse/ROO-228.

Hopefully this will help you to integrate jasperreports to your Spring-Roo based project. If you have any suggestions to make these configurations better, please let me know.