We have seen in our previous chapters, data displayed on the report is usually fetched from report parameters and report fields. This data can be processed using the report variables and their expressions. There are situations when a complex functionality cannot be achieved easily using report expressions or variables. Examples of this may be complex String manipulations, building of Maps, or Lists of objects in memory or manipulations of dates using 3rd party Java APIs. For such situations, JasperReports provides us with a simple and powerful means of doing this with Scriptlets.
Scriptlets are sequences of Java code that are executed every time a report event occurs. Values of report variables can be affected through scriptlets.
We can declare a scriptlet in two ways −
Using <scriptlet> element. This element has name attribute and class attribute. The class attribute should specify the name of the class, which extends JRAbstractScriptlet class. The class must be available in the classpath at report filling time and must have an empty constructor, so that the engine can instantiate it on the fly.
Using the attribute scriptletClass of the element <jasperReport>, in the report template (JRXML). By setting this attribute with fully qualified name of scriptlet (including the entire package name), we indicate that we want to use a scriptlet. The scriptlet instance, created with this attribute, acts like the first scriptlet in the list of scriptlets and has the predefined name REPORT.
A scriptlet is a java class, which must extend either of the following classes −
net.sf.jasperreports.engine.JRAbstractScriptlet − This class contains a number of abstract methods that must be overridden in every implementation. These methods are called automatically by JasperReports at the appropriate moment. Developer must implement all the abstract methods.
net.sf.jasperreports.engine.JRDefaultScriptlet − This class contains default empty implementations of every method in JRAbstractScriptlet. A developer is only required to implement those methods he/she needs for his/her project.
The following table lists the methods in the above class. These methods will be called by the report engine at the appropriate time, during report filling phase.
S.NO | Method and Description |
---|---|
1 | public void beforeReportInit() Called before report initialization. |
2 | public void afterReportInit() Called after report initialization. |
3 | public void beforePageInit() Called before each page is initialized. |
4 | public void afterPageInit() Called after each page is initialized. |
5 | public void beforeColumnInit() Called before each column is initialized. |
6 | public void afterColumnInit() Called after each column is initialized. |
7 | public void beforeGroupInit(String groupName) Called before the group specified in the parameter is initialized. |
8 | public void afterGroupInit(String groupName) Called after the group specified in the parameter is initialized. |
9 | public void beforeDetailEval() Called before each record in the detail section of the report is evaluated. |
10 | public void afterDetailEval() Called after each record in the detail section of the report is evaluated. |
Any number of scriptlets can be specified per report. If no scriptlet is specified for a report, the engine still creates a single JRDefaultScriptlet instance and registers it with the built-in REPORT_SCRIPTLET parameter.
We can add any additional methods that we need to our scriptlets. Reports can call these methods by using the built-in parameter REPORT_SCRIPTLET.
We can associate scriptlets in another way to reports, which is by declaring the scriptlets globally. This makes the scriptlets apply to all reports being filled in the given JasperReports deployment. This is made easy by the fact that scriptlets can be added to JasperReports as extensions. The scriptlet extension point is represented by the net.sf.jasperreports.engine.scriptlets.ScriptletFactory interface. JasperReports will load all scriptlet factories available through extensions at runtime. Then, it will ask each one of them for the list of scriptlets instances that they want to apply to the current report that is being run. When asking for the list of scriptlet instances, the engine gives some context information that the factory could use in order to decide, which scriptlets actually apply to the current report.
Governors are just an extension of global scriptlets that enable us to tackle a problem of report engine entering infinite loop at runtime, while generating reports. Invalid report templates cannot be detected at design time, because most of the time, the conditions for entering the infinite loops depend on the actual data that is fed into the engine at runtime. Report Governors help in deciding whether a certain report has entered an infinite loop and they can stop it. This prevents resource exhaustion for the machine that runs the report.
JasperReports has two simple report governors that would stop a report execution based on a specified maximum number of pages or a specified timeout interval. They are −
net.sf.jasperreports.governors.MaxPagesGovernor − This is a global scriptlet that is looking for two configuration properties to decide if it applies or not to the report currently being run. The configuration properties are −
net.sf.jasperreports.governor.max.pages.enabled=[true|false]
net.sf.jasperreports.governor.max.pages=[integer]
net.sf.jasperreports.governors.TimeoutGovernor− This is also a global scriptlet that is looking for the following two configuration properties to decide if it applies or not.
The configuration properties are −
net.sf.jasperreports.governor.timeout.enabled=[true|false]
net.sf.jasperreports.governor.timeout=[milliseconds]
The properties for both governors can be set globally, in the jasperreports.properties file, or at report level, as custom report properties. This is useful because different reports can have different estimated size or timeout limits and also because you might want turn on the governors for all reports, while turning it off for some, or vice-versa.
Let's write a scriptlet class (MyScriptlet). The contents of file C:\tools\jasperreports-5.0.1\test\src\com\howcodex\MyScriptlet.java are as follows −
package com.howcodex; import net.sf.jasperreports.engine.JRDefaultScriptlet; import net.sf.jasperreports.engine.JRScriptletException; public class MyScriptlet extends JRDefaultScriptlet { public void afterReportInit() throws JRScriptletException{ System.out.println("call afterReportInit()"); // this.setVariableValue("AllCountries", sbuffer.toString()); this.setVariableValue("someVar", new String("This variable value was modified by the scriptlet.")); } public String hello() throws JRScriptletException { return "Hello! I'm the report's scriptlet object."; } }
Details of the above scriptlet class are as follows −
In the afterReportInit method, we set a value to the variable "someVar" this.setVariableValue("someVar", new String("This variable value was modified by the scriptlet.")).
At the end of the class, an extra method called 'hello' has been defined. This is an example of a method that can be added to the Scriptlet that actually returns a value, rather than setting a Variable.
Next, we will add the scriptlet class reference in our existing report template (Chapter Report Designs). The revised report template (jasper_report_template.jrxml) are as follows. Save it to C:\tools\jasperreports-5.0.1\test directory −
<?xml version = "1.0"?> <!DOCTYPE jasperReport PUBLIC "//JasperReports//DTD Report Design//EN" "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd"> <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 = "jasper_report_template" pageWidth = "595" pageHeight = "842" columnWidth = "515" leftMargin = "40" rightMargin = "40" topMargin = "50" bottomMargin = "50" scriptletClass = "com.howcodex.MyScriptlet"> <style name = "alternateStyle" fontName = "Arial" forecolor = "red"> <conditionalStyle> <conditionExpression> <![CDATA[new Boolean($V{countNumber}.intValue() % 2 == 0)]]> </conditionExpression> <style forecolor = "blue" isBold = "true"/> </conditionalStyle> </style> <parameter name = "ReportTitle" class = "java.lang.String"/> <parameter name = "Author" class = "java.lang.String"/> <queryString> <![CDATA[]]> </queryString> <field name = "country" class = "java.lang.String"> <fieldDescription> <![CDATA[country]]> </fieldDescription> </field> <field name = "name" class = "java.lang.String"> <fieldDescription> <![CDATA[name]]> </fieldDescription> </field> <variable name = "countNumber" class = "java.lang.Integer" calculation = "Count"> <variableExpression>< ![CDATA[Boolean.TRUE]]> </variableExpression> </variable> <variable name = "someVar" class = "java.lang.String"> <initialValueExpression> <![CDATA["This is the initial variable value."]]> </initialValueExpression> </variable> <title> <band height = "100"> <line> <reportElement x = "0" y = "0" width = "515" height = "1"/> </line> <textField isBlankWhenNull = "true" bookmarkLevel = "1"> <reportElement x = "0" y = "10" width = "515" height = "30"/> <textElement textAlignment = "Center"> <font size = "22"/> </textElement> <textFieldExpression class = "java.lang.String"> <![CDATA[$P{ReportTitle}]]> </textFieldExpression> <anchorNameExpression> <![CDATA["Title"]]> </anchorNameExpression> </textField> <textField isBlankWhenNull = "true"> <reportElement x = "0" y = "40" width = "515" height = "20"/> <textElement textAlignment = "Center"> <font size = "10"/> </textElement> <textFieldExpression class = "java.lang.String"> <![CDATA[$P{Author}]]> </textFieldExpression> </textField> <textField isBlankWhenNull = "true"> <reportElement x = "0" y = "50" width = "515" height = "30" forecolor = "#993300"/> <textElement textAlignment = "Center"> <font size = "10"/> </textElement> <textFieldExpression class = "java.lang.String"> <![CDATA[$V{someVar}]]> </textFieldExpression> </textField> </band> </title> <columnHeader> <band height = "23"> <staticText> <reportElement mode = "Opaque" x = "0" y = "3" width = "535" height = "15" backcolor = "#70A9A9" /> <box> <bottomPen lineWidth = "1.0" lineColor = "#CCCCCC" /> </box> <textElement /> <text> <![CDATA[]]> </text> </staticText> <staticText> <reportElement x = "414" y = "3" width = "121" height = "15" /> <textElement textAlignment = "Center" verticalAlignment = "Middle"> <font isBold = "true" /> </textElement> <text><![CDATA[Country]]></text> </staticText> <staticText> <reportElement x = "0" y = "3" width = "136" height = "15" /> <textElement textAlignment = "Center" verticalAlignment = "Middle"> <font isBold = "true" /> </textElement> <text><![CDATA[Name]]></text> </staticText> </band> </columnHeader> <detail> <band height = "16"> <staticText> <reportElement mode = "Opaque" x = "0" y = "0" width = "535" height = "14" backcolor = "#E5ECF9" /> <box> <bottomPen lineWidth = "0.25" lineColor = "#CCCCCC" /> </box> <textElement /> <text> <![CDATA[]]> </text> </staticText> <textField> <reportElement style = "alternateStyle" x="414" y = "0" width = "121" height = "15" /> <textElement textAlignment = "Center" verticalAlignment = "Middle"> <font size = "9" /> </textElement> <textFieldExpression class = "java.lang.String"> <![CDATA[$F{country}]]> </textFieldExpression> </textField> <textField> <reportElement x = "0" y = "0" width = "136" height = "15" /> <textElement textAlignment = "Center" verticalAlignment = "Middle" /> <textFieldExpression class = "java.lang.String"> <![CDATA[$F{name}]]> </textFieldExpression> </textField> </band> </detail> <summary> <band height = "45"> <textField isStretchWithOverflow = "true"> <reportElement x = "0" y = "10" width = "515" height = "15" /> <textElement textAlignment = "Center"/> <textFieldExpression class = "java.lang.String"> <![CDATA["There are " + String.valueOf($V{REPORT_COUNT}) + " records on this report."]]> </textFieldExpression> </textField> <textField isStretchWithOverflow = "true"> <reportElement positionType = "Float" x = "0" y = "30" width = "515" height = "15" forecolor = "# 993300" /> <textElement textAlignment = "Center"> <font size = "10"/> </textElement> <textFieldExpression class = "java.lang.String"> <![CDATA[$P{REPORT_SCRIPTLET}.hello()]]> </textFieldExpression> </textField> </band> </summary> </jasperReport>
The details of the revised report template is given below −
We have referenced the MyScriptlet class in the attribute scriptletClass of <jasperReport> element.
Scriptlets can only access, but not modify the report fields and parameters. However, scriptlets can modify report variable values. This can be accomplished by calling the setVariableValue() method. This method is defined in JRAbstractScriptlet class, which is always the parent class of any scriptlet. Here, we have defined a variable someVar, which will be modified by the MyScriptlet to have the value This value was modified by the scriptlet.
The above report template has a method call in the Summary band that illustrates how to write new methods (in scriptlets) and use them in the report template. ($P{REPORT_SCRIPTLET}.hello())
The java codes for report filling remain unchanged. The contents of the file C:\tools\jasperreports-5.0.1\test\src\com\howcodex\JasperReportFill.java are as given below −
package com.howcodex; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JasperFillManager; import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; public class JasperReportFill { @SuppressWarnings("unchecked") public static void main(String[] args) { String sourceFileName = "C://tools/jasperreports-5.0.1/test/jasper_report_template.jasper"; DataBeanList DataBeanList = new DataBeanList(); ArrayList<DataBean> dataList = DataBeanList.getDataBeanList(); JRBeanCollectionDataSource beanColDataSource = new JRBeanCollectionDataSource(dataList); Map parameters = new HashMap(); /** * Passing ReportTitle and Author as parameters */ parameters.put("ReportTitle", "List of Contacts"); parameters.put("Author", "Prepared By Manisha"); try { JasperFillManager.fillReportToFile( sourceFileName, parameters, beanColDataSource); } catch (JRException e) { e.printStackTrace(); } } }
The contents of the POJO file C:\tools\jasperreports-5.0.1\test\src\com\howcodex\DataBean.java are as given below −
package com.howcodex; public class DataBean { private String name; private String country; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } }
The contents of the file C:\tools\jasperreports-5.0.1\test\src\com\howcodex\DataBeanList.java are as given below −
package com.howcodex; import java.util.ArrayList; public class DataBeanList { public ArrayList<DataBean> getDataBeanList() { ArrayList<DataBean> dataBeanList = new ArrayList<DataBean>(); dataBeanList.add(produce("Manisha", "India")); dataBeanList.add(produce("Dennis Ritchie", "USA")); dataBeanList.add(produce("V.Anand", "India")); dataBeanList.add(produce("Shrinath", "California")); return dataBeanList; } /** * This method returns a DataBean object, * with name and country set in it. */ private DataBean produce(String name, String country) { DataBean dataBean = new DataBean(); dataBean.setName(name); dataBean.setCountry(country); return dataBean; } }
We will compile and execute the above file using our regular ANT build process. The contents of the file build.xml (saved under directory C:\tools\jasperreports-5.0.1\test) are as given below.
The import file - baseBuild.xml is picked up from the chapter Environment Setup and should be placed in the same directory as the build.xml.
<?xml version = "1.0" encoding = "UTF-8"?> <project name = "JasperReportTest" default = "viewFillReport" basedir = "."> <import file = "baseBuild.xml" /> <target name = "viewFillReport" depends = "compile,compilereportdesing,run" description = "Launches the report viewer to preview the report stored in the .JRprint file."> <java classname = "net.sf.jasperreports.view.JasperViewer" fork = "true"> <arg value = "-F${file.name}.JRprint" /> <classpath refid = "classpath" /> </java> </target> <target name = "compilereportdesing" description = "Compiles the JXML file and produces the .jasper file."> <taskdef name = "jrc" classname = "net.sf.jasperreports.ant.JRAntCompileTask"> <classpath refid = "classpath" /> </taskdef> <jrc destdir = "."> <src> <fileset dir = "."> <include name = "*.jrxml" /> </fileset> </src> <classpath refid = "classpath" /> </jrc> </target> </project>
Next, let's open command line window and go to the directory where build.xml is placed. Finally, execute the command ant -Dmain-class=com.howcodex.JasperReportFill (viewFullReport is the default target) as −
C:\tools\jasperreports-5.0.1\test>ant -Dmain-class=com.howcodex.JasperReportFill Buildfile: C:\tools\jasperreports-5.0.1\test\build.xml clean-sample: [delete] Deleting directory C:\tools\jasperreports-5.0.1\test\classes [delete] Deleting: C:\tools\jasperreports-5.0.1\test\jasper_report_template.jasper [delete] Deleting: C:\tools\jasperreports-5.0.1\test\jasper_report_template.jrprint compile: [mkdir] Created dir: C:\tools\jasperreports-5.0.1\test\classes [javac] C:\tools\jasperreports-5.0.1\test\baseBuild.xml:28: warning: 'includeantruntime' was not set, defaulting to bu [javac] Compiling 4 source files to C:\tools\jasperreports-5.0.1\test\classes compilereportdesing: [jrc] Compiling 1 report design files. [jrc] log4j:WARN No appenders could be found for logger (net.sf.jasperreports.engine.xml.JRXmlDigesterFactory). [jrc] log4j:WARN Please initialize the log4j system properly. [jrc] log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. [jrc] File : C:\tools\jasperreports-5.0.1\test\jasper_report_template.jrxml ... OK. run: [echo] Runnin class : com.howcodex.JasperReportFill [java] log4j:WARN No appenders could be found for logger (net.sf.jasperreports.extensions.ExtensionsEnvironment). [java] log4j:WARN Please initialize the log4j system properly. [java] call afterReportInit() [java] call afterReportInit() viewFillReport: [java] log4j:WARN No appenders could be found for logger (net.sf.jasperreports.extensions.ExtensionsEnvironment). [java] log4j:WARN Please initialize the log4j system properly. BUILD SUCCESSFUL Total time: 18 minutes 49 seconds
As a result of above compilation, a JasperViewer window opens up as shown in the screen given below −
Here we see two messages are displayed from MyScriptlet class −