Monday, April 25, 2011

Spring + JasperReports

Hey...

Reports are one of the major parts of an enterprise application. There are some open source reporting tools for java applications like JasperReports, JFreeReport, JXLS, and Eclipse BIRT.  Here I'm going to describe one of the powerful reporting tools called JasperReports which can be embedded in your java application.


JasperReports
JasperReports is an open source Java reporting tool that can write to screen, to a printer or into PDF, HTML, Microsoft Excel, RTF, ODT, Comma-separated values and XML files.

It can be used in Java-enabled applications, including Java EE or Web applications, to generate dynamic content. It reads its instructions from an XML or .jasper file.

JasperReports is part of the Lisog open source stack initiative.

Features
JasperReports is an open source reporting library that can be embedded into any Java application. Features include:
  • Scriptlets may accompany the report definition, which the report definition can invoke at any point to perform additional processing. The scriptlet is built using Java, and has many hooks that can be invoked before or after stages of the report generation, such as Report, Page, Column or Group.
  • Sub-reports
For users with more sophisticated report management requirements, reports designed for JasperReports can be easily imported into the JasperServer - the interactive report server.

JRXML
JasperReports reports are defined in an XML file format, called JRXML, which can be hand-coded, generated, or designed using a tool. The file format is defined by a Document Type Definition (DTD), providing limited interoperability.

The main difference between using XML and a .jasper file is that the XML file should be compiled at runtime using the JasperCompileManager class.

Third party tools
There are many tools providing JasperReport capabilities:
  • iReport, an open source standalone graphical program that provides report designer capabilities, and is able to run reports using all data source supported by the JasperReports engine. iReport is actively maintained by JasperSoft.
  • DynamicReports, an open source Java API reporting library based on JasperReports which lets you create sophisticated reports at the lowest way without need to use visual designer, main benefit of this library is dynamic report design.
  • SWTJasperViewer, an open source reusable component that can be embedded in any SWT/JFace application such as Eclipse.
  • Report Integration Framework, an open source report abstraction layer.
  • five Eclipse plug-ins that provide report designing and debugging capabilities, including:
    • JasperSoft Studio (Still in Alpha) is a rewrite of iReports in Eclipse
    • a commercial Eclipse plug-in called JasperAssistant. The JasperAssistant plug-in is built using SWTJasperViewer.
  • WebReportBuilder, an open source Java EE web application that allows web based developers and non developers to create basic and advanced Reports based on JasperReports to be used as a Web Report Server.
  • OpenReports, a Java EE web application that provides advanced report server capabilities with support for four open source reporting engines: JasperReports, JFreeReport, JXLS, and Eclipse BIRT.
  • JasperTags, a JSP tag library for easy inclusion of reports in web applications.
  • Plazma Report Designer, an open source JasperReports designer plugin for Eclipse.
  • Aspose.Words for JasperReports, for converting reports from JasperReports and JasperServer to Word formats.
  • Aspose.Slides for JasperReports, for converting to PowerPoint PPT and PPS formats.
  • The Information Management System for Mine Action (IMSMA) uses JasperReports for its reporting capability. It is the most commonly used planning software for humanitarian demining.
Sample
Now I'm going to take a sample and describe more about JasperReports via this sample. I've had a post about JQGrid plugin. I'm going to extend the sample of that post. I'll make a report page for file list result.

JasperReportView class
I make a class called JasperReportView to handle JasperReports needed codes. Here is the source code:
public class JasperReportView {

    public JasperReportView(HttpServletResponse response) {
        this.response = response;
    }

    HttpServletResponse response;
    JasperReport jasperReport;
    JasperPrint jasperPrint;
    JasperDesign jasperDesign;
    byte[] pdfReport;

    public void renderPdfByJasper(String jasper, Map parameters) throws IOException, JRException, SQLException {
        InputStream is = this.getClass().getResource(jasper).openStream();
        try {
            jasperReport = (JasperReport) JRLoader.loadObject(is);
            jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, SpringUtils.<DriverManagerDataSource>getBean("dataSource").getConnection());
            pdfReport = JasperExportManager.exportReportToPdf(jasperPrint);
            response.setContentType("APPLICATION/OCTET-STREAM");
            response.addHeader("Content-Disposition", "attachment; filename=report.pdf");
            OutputStream out = response.getOutputStream();
            out.write(pdfReport);
            out.close();
        }
        finally {
            is.close();
        }
    }

    public void renderPdfByJrxml(String jrxml, Map parameters) throws IOException, JRException, SQLException {
        InputStream is = this.getClass().getResource(jrxml).openStream();
        try {
            jasperDesign = JRXmlLoader.load(is);
            jasperReport = JasperCompileManager.compileReport(jasperDesign);
            jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, SpringUtils.<DriverManagerDataSource>getBean("dataSource").getConnection());
            pdfReport = JasperExportManager.exportReportToPdf(jasperPrint);
            response.setContentType("APPLICATION/OCTET-STREAM");
            response.addHeader("Content-Disposition", "attachment; filename=report.pdf");
            OutputStream out = response.getOutputStream();
            out.write(pdfReport);
            out.close();
        }
        finally {
            is.close();
        }
    }

    public void renderHtmlByJasper(String jasper, Map parameters) throws IOException, JRException, SQLException {
        ...
    }

    public void renderHtmlByJrxml(String jrxml, Map parameters) throws IOException, JRException, SQLException {
        ...
    }

    public void renderCsvByJasper(String jasper, Map parameters) throws IOException, JRException, SQLException {
        ...
    }

    public void renderCsvByJrxml(String jrxml, Map parameters) throws IOException, JRException, SQLException {
        ...
    }
}
As you see the red lines are the code to work with .jasper and the blue lines are the code to work with .jrxml.
As I mentioned the .jrxml files should be compiled at run time so it will take more time.
CSV and Html version export look likes Pdf version.The green lines are the codes to download the exported files.

How to use JasperReportView class
I should just make an object instance from JasperReportView class and use any method we need. The class needs HttpServletResponse as constructor parameter. Because it should download the exported file in its methods. Every method needs file path and report needed parameters as a Map.
FileController class is a class to handle file page actions. Take a look here:
@Controller
@Scope("prototype")
@RequestMapping("/file")
public class FileController extends BaseController {
...

    @RequestMapping("/pdf")
    public void pdfShow() throws IOException, SQLException, JRException {
        JasperReportView jrv = new JasperReportView(getResponse());
        if (getParentFolder().getId() == null) {
//        jrv.renderPdfByJrxml("/com/ucs/file/file-root.jrxml", getReportParams());
            jrv.renderPdfByJasper("/com/ucs/file/file-root.jasper", getReportParams());
        } else {
//        jrv.renderPdfByJrxml("/com/ucs/file/folder-file.jrxml", getReportParams());
            jrv.renderPdfByJasper("/com/ucs/file/folder-file.jasper", getReportParams());
        }
    }

    @RequestMapping("/csv")
    public void csvShow() throws IOException, SQLException, JRException {
        JasperReportView jrv = new JasperReportView(getResponse());
        if (getParentFolder().getId() == null) {
//        jrv.renderCsvByJrxml("/com/ucs/file/file-root.jrxml", getReportParams());
            jrv.renderCsvByJasper("/com/ucs/file/file-root.jasper", getReportParams());
        } else {
//        jrv.renderCsvByJrxml("/com/ucs/file/folder-file.jrxml", getReportParams());
            jrv.renderCsvByJasper("/com/ucs/file/folder-file.jasper", getReportParams());
        }
    }

    @RequestMapping("/html")
    public void htmlShow() throws IOException, SQLException, JRException {
        JasperReportView jrv = new JasperReportView(getResponse());
        if (getParentFolder().getId() == null) {
//        jrv.renderHtmlByJrxml("/com/ucs/file/file-root.jrxml", getReportParams());
            jrv.renderHtmlByJasper("/com/ucs/file/file-root.jasper", getReportParams());
        } else {
//        jrv.renderHtmlByJrxml("/com/ucs/file/folder-file.jrxml", getReportParams());
            jrv.renderHtmlByJasper("/com/ucs/file/folder-file.jasper", getReportParams());
        }
    }


    public Map<String, Object> getReportParams() {
        Map<String, Object> params = new HashMap();
        params.put("parentFolderId", getParentFolder().getId());
        params.put("ownerId", getCurrentUser().getId());
        return params;
    }
...

}
The report needs two parameters:
  • parentFolderId
  • ownerId
So I put them in a map and pass it to the JasperReportView methods(red lines). I do it in getReportParams method(green lines).

Appendix

1. SpringUtils Class
It's a very useful class to provide you Spring Context. On this way you can have spring beans in you java code. This class should Implements ApplicationContextAware class and setApplicationContext method. Take look here:
public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext context;

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.context = context;
    }

    public static <T> T getBean(String beanName) {
        return (T) context.getBean(beanName);
    }

    public static <T> Map<String, T> getBeans(Class clazz) {
        return (Map<String, T>) context.getBeansOfType(clazz);
    }

    public static HttpSession getCurrentSession() {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        return attr.getRequest().getSession(true); // true == allow create
    }

    public static HttpServletRequest getCurrentRequest() {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        return attr.getRequest();
    }
}

As you see the setApplicationContext method will pass you a context in its parameter. I take it in the class static field. So I will use it in getBean and getBeans methods. This class also provide you current session and current request as static methods(The green lines). So I can use them anywhere I need session and request.

2. JRXML files
jrxml is JasperReports xml file. Reports elements like page header, footer, Grid header, repeat items, ... will be determined in it. Here file-root.jrxml example is available:
<jasperReport
         name="file"
         columnCount="1"
         printOrder="Vertical"
         orientation="Portrait"
         pageWidth="595"
         pageHeight="842"
         columnWidth="535"
         columnSpacing="0"
         leftMargin="30"
         rightMargin="30"
         topMargin="20"
         bottomMargin="20"
         whenNoDataType="NoPages"
         isTitleNewPage="false"
         isSummaryNewPage="false">
    <property name="ireport.scriptlethandling" value="0" />
    <property name="ireport.encoding" value="UTF-8" />
    <import value="java.util.*" />
    <import value="net.sf.jasperreports.engine.*" />
    <import value="net.sf.jasperreports.engine.data.*" />
    <reportFont name="bnazanin" isDefault="false" fontName="Nazanin" size="14" isBold="false" isItalic="false" isUnderline="false" isStrikeThrough="false" pdfFontName="NAZ___SF.TTF" pdfEncoding="Identity-H" isPdfEmbedded="true"/>

    <parameter name="parentFolderId" isForPrompting="true" class="java.lang.Long">
        <defaultValueExpression ><![CDATA[new Long(0)]]></defaultValueExpression>
    </parameter>
    <parameter name="ownerId" isForPrompting="true" class="java.lang.Long">
        <defaultValueExpression ><![CDATA[new Long(0)]]></defaultValueExpression>
    </parameter>
    <queryString><![CDATA[SELECT `name`, `is_file` FROM `tb_file` where owner_id = $P{ownerId} and parent_file_id IS NULL;]]></queryString>

    <field name="name" class="java.lang.String"/>
    <field name="is_file" class="java.lang.String"/>

        <background>
            <band height="0"  isSplitAllowed="true" >
            </band>
        </background>
        <title>
            <band height="50"  isSplitAllowed="true" >
            </band>
        </title>
        <pageHeader>
            <band height="36"  isSplitAllowed="true" >
            </band>
        </pageHeader>
        <columnHeader>
            <band height="75"  isSplitAllowed="true" >
                <staticText>
                    <reportElement
                        x="4"
                        y="52"
                        width="97"
                        height="20"
                        key="staticText-2"/>
                    <box topBorder="Thin" topBorderColor="#000000" leftBorder="Thin" leftBorderColor="#000000" rightBorder="Thin" rightBorderColor="#000000" bottomBorder="Thin" bottomBorderColor="#000000"/>
                    <textElement>
                        <font reportFont="bnazanin"/>
                    </textElement>
                <text><![CDATA[File Name]]></text>
                </staticText>
                <staticText>
                    <reportElement
                        x="101"
                        y="52"
                        width="97"
                        height="20"
                        key="staticText-3"/>
                    <box topBorder="Thin" topBorderColor="#000000" leftBorder="Thin" leftBorderColor="#000000" rightBorder="Thin" rightBorderColor="#000000" bottomBorder="Thin" bottomBorderColor="#000000"/>
                    <textElement>
                        <font reportFont="bnazanin"/>
                    </textElement>
                <text><![CDATA[File type]]></text>
                </staticText>
            </band>
        </columnHeader>
        <detail>
            <band height="25"  isSplitAllowed="true" >
                <textField isStretchWithOverflow="false" isBlankWhenNull="false" evaluationTime="Now" hyperlinkType="None"  hyperlinkTarget="Self" >
                    <reportElement
                        x="3"
                        y="0"
                        width="95"
                        height="18"
                        key="textField"/>
                    <box topBorder="None" topBorderColor="#000000" leftBorder="None" leftBorderColor="#000000" rightBorder="None" rightBorderColor="#000000" bottomBorder="None" bottomBorderColor="#000000"/>
                    <textElement>
                        <font reportFont="bnazanin"/>
                    </textElement>
                <textFieldExpression   class="java.lang.String"><![CDATA[$F{name}]]></textFieldExpression>
                </textField>
                <textField isStretchWithOverflow="false" isBlankWhenNull="false" evaluationTime="Now" hyperlinkType="None"  hyperlinkTarget="Self" >
                    <reportElement
                        x="99"
                        y="1"
                        width="100"
                        height="18"
                        key="textField"/>
                    <box topBorder="None" topBorderColor="#000000" leftBorder="None" leftBorderColor="#000000" rightBorder="None" rightBorderColor="#000000" bottomBorder="None" bottomBorderColor="#000000"/>
                    <textElement>
                        <font reportFont="bnazanin"/>
                    </textElement>
                <textFieldExpression   class="java.lang.String"><![CDATA[$F{is_file}]]></textFieldExpression>
                </textField>
            </band>
        </detail>
        <columnFooter>
            <band height="30"  isSplitAllowed="true" >
            </band>
        </columnFooter>
        <pageFooter>
            <band height="50"  isSplitAllowed="true" >
            </band>
        </pageFooter>
        <lastPageFooter>
            <band height="50"  isSplitAllowed="true" >
            </band>
        </lastPageFooter>
        <summary>
            <band height="50"  isSplitAllowed="true" >
            </band>
        </summary>
</jasperReport>

Source Code
Now you can download the sample and try it yourselves. You can download the needed jar files from this sample. You need to copy all jar files to [Project Root]/lib path.
The application database script is available in [app-root]/db/filerepository.sql. you can restore it in your mysql server. the connection datasource properties is in [app-root]/src/database.properites. And after you deploy the project in your application server (like tomcat) the home page will be: http://localhost:8080/home/view.html also file list page is: http://localhost:8080/file/list.html

Make sure JaperReports needed files(file-root.jasper, file-root.jrxml, folder-file.jasper and folder-file.jrxml) exist in your deployment path. If not copy them into WEB-INF/classes/com/ucs/file/ path by yourselves.

the admin user specification is:
username: administrator
password: 123456
you can use it to login to the site for the first time.



all rights reserved by Mostafa Rastgar and Programmer Assistant weblog

1 comment:

mkr said...

Very good summary of JasperReports.But I have another kind of problem i hope u can help me. iam using struts,ejb and jasper reports with ireports,iam confusing about how to pass dynamic parameters at runtime through java to jasper reports?