Sunday, February 12, 2012

Using JasperReports

JasperReports is a java report library that can be used to add reporting capability to an application.

Using JasperReports in a Java EE application


Prerequisistes

JRE
Servlet engine
RDBMS + JDBC driver for a database
Apache Ant

Versions used
JRE 1.6.0_21
Apache Tomcat 6.0.29
MySQL 5.1.50
MySQL connector/J 5.1.13
JasperReports 3.7.0
Apache Ant 1.8.1

Steps
  • Download the jasperreports project zip file from sourceforge, http://sourceforge.net/projects/jasperreports/
  • Extract the zip file to a temporary location e.g. C:\temp
  • Open a command prompt
  • Navigate to c:\temp\jasperreports-3.7.0-project\jasperreports-3.7.0\demo\samples\webapp
  • Type the command ant javac [the path to the ant bin folder needs to be in the system path]
  • Create a folder in the tomcat webapps directory e.g. jasper
  • Copy the contents of the webapp directory to the webapps\jasper directory. This sample web application can serve as a guide on how to integrate jasperreports into your own web application.


Using in a custom application
  • Copy the following files from the webapps\jasper\web-inf\lib\ directory to your application's web-inf\lib directory. commons-beanutils-1.8.0.jar, commons-collections-2.1.1.jar, commons-digester-1.7.jar, commons-logging-1.0.4.jar, jasperreports-3.7.0.jar, iText-2.1.0.jar (for pdf export), poi-3.2-FINAL-20081019.jar (for xls export). For generating charts you'll need jfreechart and jcommon.


Generating a report
import net.sf.jasperreports.engine.*; //for main jasper objects
import net.sf.jasperreports.engine.export.*; //for exporters

JasperReport jasperReport;
JasperPrint jasperPrint;
JRResultSetDataSource ds;

ServletContext context = this.getServletConfig().getServletContext();  
String fileBase="report1";

String fileName=context.getRealPath("/reports/"+fileBase+".jasper");

File reportFile = new File(context.getRealPath("/reports/"+fileBase+".jasper"));
if (!reportFile.exists()) {
//compile file
fileName=JasperCompileManager.compileReportToFile(context.getRealPath("/reports/"+fileBase+".jrxml"));   
}

//rs is a resultset generated by your application. You can also pass a connection instead of a resultset if you want to use the query as it is in the repoort template    
ds = new JRResultSetDataSource(rs);

HashMap parameters=new HashMap(); //use a map to pass report parameters  
jasperPrint = JasperFillManager.fillReport(fileName, parameters,ds);

//export to pdf   
JasperExportManager.exportReportToPdfFile(jasperPrint,fullFileName);

//export to html
JRXhtmlExporter exporter = new JRXhtmlExporter();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRExporterParameter.OUTPUT_FILE_NAME, fullFileName);
exporter.exportReport();

//export to xls
JRXlsExporter exporter = new JRXlsExporter();    
exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRExporterParameter.OUTPUT_FILE_NAME, fullFileName);
exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.FALSE);
exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
exporter.exportReport();


Passing parameters
  • If passing parameters, the parameter value data type in the parameters map must match the data type of the parameter as declared in the report template.

Example template query using a single value parameter
select * from customers where customer_id = $P{customer_id_param}
Example template query using a multi value parameter
select * from products where $X{IN, category, categories_param}

select * from products where $X{NOTIN, category, categories_param}
For multi value parameters, define the parameter class in the jrxml file as java.util.List. Pass values using an array list e.g
List categories = new ArrayList();
categories.add("Laptop");
categories.add("PC");
categories.add("Printer");
parameters.put("categories_param", categories);

Using virtualizers
  • A virtualizer can be used to enable large reports to be generated successfully, without resulting in out of memory errors. Using a virtualizer doesn't guarantee filling of arbitrarily large reports. Heap memory is still needed but memory requirements are reduced.
  • Also, once a report is filled, whether it can be exported successfully depends on the export format and library. e.g. Filling may be successful, generating a jasper print object but exporting to excel may fail because poi puts all the data in memory before generating the final file. Adding available heap memory may help to have a successful export.
  • If a report contains images, this may also be a cause of out of memory errors. In such a case, the virtualizer doesn't help until you set the image's properties "isUsingCache" to false and "isLazy" to true in the report template.
import net.sf.jasperreports.engine.fill.*; //for virtualizers
import net.sf.jasperreports.engine.util.*; //for jrswapfile

//use virtualizer if required
JRAbstractLRUVirtualizer virtualizer=null;

if(!props.getProperty(VIRTUALIZER).equals("none")){
if(props.getProperty(VIRTUALIZER).equals("file")){
int maxSize=Integer.parseInt(props.getProperty(FILE_MAX_SIZE));
virtualizer=new JRFileVirtualizer(maxSize,System.getProperty("java.io.tmpdir"));
params.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);
} else if(props.getProperty(VIRTUALIZER).equals("gzip")){
int maxSize=Integer.parseInt(props.getProperty(GZIP_MAX_SIZE));
virtualizer=new JRGzipVirtualizer(maxSize);
params.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);
} else {
//use swap virtualizer by default
int maxSize=Integer.parseInt(props.getProperty(SWAP_MAX_SIZE));
int blockSize=Integer.parseInt(props.getProperty(SWAP_BLOCK_SIZE));
int minGrowCount=Integer.parseInt(props.getProperty(SWAP_MIN_GROW_COUNT));

JRSwapFile swapFile=new JRSwapFile(System.getProperty("java.io.tmpdir"),blockSize,minGrowCount);
virtualizer=new JRSwapFileVirtualizer(maxSize,swapFile);
params.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);
}
}

//fill report with data
JasperPrint jasperPrint;
if(rs==null){
//use template query
connQuery = ArtDBCP.getConnection(datasourceId);      
jasperPrint = JasperFillManager.fillReport(jasperFileName, params,connQuery);
} else {
//use recordset based on art query 
JRResultSetDataSource ds;
ds = new JRResultSetDataSource(rs);
jasperPrint = JasperFillManager.fillReport(jasperFileName, params,ds);
}

//set virtualizer read only to optimize performance. must be set after print object has been generated
if(virtualizer!=null){
virtualizer.setReadOnly(true);
}

//export report
...

//clean up
if(virtualizer!=null){
virtualizer.cleanup();
}

Notes
  • If the resultset is null, by default, the generated report will consist of a completely blank page. To display other report sections and only have the data section blank, if using iReport, change the report properties "when no data" option to "all sections, no detail".
  • If the report contains images, the image files should be located in the same directory as the jrxml file
  • If a parameter is used in a query and is not provided, it will be ignored, as if the condition didn't exist

4 comments:

  1. Hello Timothy, very informative post. I was looking for exactly this code sample. Thanks for sharing.
    I have one issue. In my case code to fill report is kept on the server. This code returns the object of type JasperPrint to client. Client then exports a report.
    So part upto filling report and virtualizer read only to true is OK. But how can I clean up the virtualizer after report is exported (as export code is on client and virtualizer was created and used on server).
    What if I clean up the virtualizer before I export the report?

    ReplyDelete
  2. Not sure what should happen. You can try out the different scenarios and see what happens.

    ReplyDelete
  3. Hello Timothy, Very nice and helpful blog, I do have a out of memory exception but the data is very small. say 2 - 3 rows. But still i am getting out of memory exception. Do you have any inputs on this?

    ReplyDelete
  4. I don't know why that would be happening.

    ReplyDelete