Jasper reports - fieldenms/tg GitHub Wiki
Here you will learn how to create a Jasper report, include it into TG based application, produce a PDF file, and how to present it for printing or email it as an attachment.
- Tools
- Templates
- How to include a Jasper report into TG based application
- How to produce a PDF file and present it for printing
- How to email PDF file as attachment
- Running iReport on macOS
To develop a Jasper report for TG-based projects Jaspersoft iReport Designer 4.7.1 should be used.
This is a separate tool and installation could be downloaded from https://ireport.software.informer.com/4.7/.
Also more detail information about Jasper Reports could be obtain here: http://jasperreports.sourceforge.net/JasperReports-Ultimate-Guide-3.pdf
There are two types of reports:
- simple report (all data is gathered from one or a few entities and is simply presented)
- more complex report; such a report will contain one or more sub-reports (in this case data usually is presented as lines, with header and grid on the background)
Often some sort of logo is presented in the report.
For example for the TG PSA project Fielden's logo is stored at the following location:
tgpsa\tgpsa\tgpsa-web-server\src\main\resources\report_templates\images\logo.png
Two templates were created for the TG PSA project: SimpleReportWithLogo.jrxml
and ReportWithSubreport.jrxml
to simplify the process of developing reports. These templates are located at tgpsa\tgpsa\tgpsa-web-server\src\main\resources\report_templates
.
Three report styles [1] are introduced: reportStyle
, reportBoldStyle
and reportStyleArialBlack
, which are used in both report and sub-report.
Style reportStyleArialBlack
is used for Report Title only.
Style reportBoldStyle
is used for labeling of data. The word Description in a report or column headers: Item No, Description, Amount in the sub-report are using this style.
Style reportStyle
is used for data fields.
To include logo into the report the report parameter imagePath [2] is introduced to provide location for all images. This parameter is used in the Image Expression of the image object to specify what picture would be shown.
To represent data in the report new Fields [3] should be added. The same is applicable to the sub-reports.
The field should be of type String
as all formatting would be done in TG. Names are case sensitive!
Better to use same rules as in TG when naming variables here and make sure when you provide real data in TG you are using the correct names.
Page Footer [4] contains some useful examples which are sometimes necessary when designing a report: today's date/time and page numbering.
A sub-report usually contains two sections: Column Header and Details.
It should contain the same Styles [1] for consistency with the main report. Please specify meaningful names for Report name [2], and Page size [3] should be the size of the area used by the sub-report in the report, this is especially important when you have a few sub-reports! The Job Pack for TTGAMS is good example where five sub-reports are presented one after the other.
The sub-report has to be compiled and as result SimpleSubreport.jasper
will be created. This Jasper file has to be committed and released to the client. The TG-based application will be looking for this Jasper file for sub-reports as only the main report could be compiled at run time.
To generate the Jasper file simply press the Preview option in the designer.
Two additional parameters are introduced: subreport and subreportData [1].
Object Subreport [2] is added into the Details section, also some rectangles to represent a grid on the background.
Some properties of a sub-report object are very important.
For example Width and Height [2] should correspond to the Page size of the sub-report.
sub-report Expression and Data Source Expression [2] should point to our additional parameters subreport
and subreportData
.
Let's use the Tax Invoice report from the TG PSA project as an example.
InvoiceReport.jrxml
was designed (it contains sub-report InvoiceLinesSubreport.jrxml
) by using the templates described above as a base during development.
The next step is to create a Java class that will collect all the necessary data, provide it to the Jasper report, and compile it into a PDF file.
InvoiceReport.java
was created in tgpsa-pojo-bl\src\main\java\fielden\project\reports\
.
This class has a public method reportAsPdf(final InvoiceHeader invoiceHeader, final IInvoiceLine coInvoiceLine)
, which does all necessary steps.
public byte[] reportAsPdf(final InvoiceHeader invoiceHeader, final IInvoiceLine coInvoiceLine) throws JRException {
// retrieve all necessary data using provided invoiceHeader
final List<Map<String, ?>> data = new ArrayList<>();
data.add(createInvoiceHeader(invoiceHeader));
// creating data source for lines sub report
final List<EntityAggregates> lines = aggregateLines(invoiceHeader, coInvoiceLine);
final JRRewindableDataSource jrsLines = new EntityAggregatesReportSource(lines.toArray(new EntityAggregates[] {}), subreportProps);
final JasperReport report = JasperCompileManager.compileReport(reportTemplatePath);
addPdfFontsToStyles(report.getStyles());
// filling report template with data and parameters
final JRMapCollectionDataSource dataSource = new JRMapCollectionDataSource(data);
final Map<String, Object> params = new HashMap<>();
params.put("imagePath", reportImagePath);
params.put("subReport", subreportTemplatePath);
params.put("subReportData", jrsLines);
final JasperPrint print = JasperFillManager.fillReport(report, params, dataSource);
// exporting to PDF file as byte array
final JRPdfExporter exporter = new JRPdfExporter();
exporter.setParameter(JRExporterParameter.CHARACTER_ENCODING, "UTF-8");
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, outputStream);
// will automatically also show "Print" dialog upon opening
exporter.setParameter(JRPdfExporterParameter.PDF_JAVASCRIPT, "this.print();");
exporter.exportReport();
return outputStream.toByteArray();
}
Method createInvoiceHeader(invoiceHeader)
retrieves all necessary data for the main report using the provided invoiceHeader
.
Method aggregateLines(invoiceHeader, coInvoiceLine)
retrieves all the necessary data for the sub-report, produces List<EntityAggregates>
that is transformed into the JRRewindableDataSource
.
After that the main report is compiled, three report styles are included, data is provided for additional parameters:
imagePath
, subReport
and subReportData
.
JasperFillManager and JRPdfExporter take care of the rest of the functionality: filling the report and producing output as a PDF file.
Usually an action has to be created to trigger the report creation and printing process.
In this particular case existing action InvoiceApprovalAction
was used and extended with printing functionality (as invoice approval triggers Tax Invoice print out by design).
Three additional properties mime
, fileName
and data
were added to the InvoiceApprovalAction
entity.
public class InvoiceApprovalAction extends AbstractFunctionalEntityWithCentreContext<String>
// the following 3 properties pertain to the creation of the PDF representation of the invoice
@IsProperty
@Readonly
@Title(value = "MIME", desc = "File MIME Type.")
private String mime; // application/pdf, application/vnd.ms-excel, text/plain, text/html
@IsProperty
@Title(value = "File Name", desc = "The name of file that Invoice report will produce. For eg. InvoiceNo_002161.pdf.")
private String fileName;
@IsProperty
@Title(value = "Data", desc = "Raw binary data that was produced by Jasper report and needs to be persisted.")
private byte[] data;
A value for property mime
is provided in the new_()
method:
@Override
public InvoiceApprovalAction new_() {
return super.new_().setMime("PDF");
}
A value for property fileName
is provided in the producer:
final String fileName = "InvoiceNo_" + invoiceHeader.getKey() + ".pdf";
entity.beginInitialising();
entity.setFileName(fileName);
entity.endInitialising();
Property data
will contain the outcome from Jasper report generation, which is done in the save()
method:
// let's generate report as PDF file with fileName
try {
final byte[] reportOutputStream = new InvoiceReport().reportAsPdf(invoiceHeader, co(InvoiceLine.class));
entity.setData(reportOutputStream);
} catch (final JRException e) {
throw Result.asRuntime(e);
}
Now to present this generated PDF file to the user for review and printing, the post action for this InvoiceApprovalAction
should be modified and new FileSaverPostAction()
used.
final EntityActionConfig approvalAction = action(InvoiceApprovalAction.class)
.withContext(context().withSelectionCrit().withSelectedEntities().build())
.postActionSuccess(new FileSaverPostAction())
.icon("icons:check-circle")
.withStyle(CUSTOM_ACTION_COLOUR)
.shortDesc("Approve selected Invoice")
.longDesc("Approve selected Invoice")
.build();
Jasper-related dependencies have to be included into pom files.
All fonts used during report development should be included in tgpsa-dao\pom.xml
for testing purposes and tgpsa\tgpsa-web-server\pom.xml
for production release.
<dependency>
<groupId>fielden</groupId>
<artifactId>jasper-fonts</artifactId>
<classifier>ariblk</classifier>
<version>1.0</version>
</dependency>
<dependency>
<groupId>fielden</groupId>
<artifactId>jasper-fonts</artifactId>
<classifier>calibri</classifier>
<version>1.0</version>
</dependency>
<dependency>
<groupId>fielden</groupId>
<artifactId>jasper-fonts</artifactId>
<classifier>times</classifier>
<version>1.0</version>
</dependency>
<dependency>
<groupId>fielden</groupId>
<artifactId>jasper-fonts</artifactId>
<classifier>wingding</classifier>
<version>1.0</version>
</dependency>
tgpsa\tgpsa-web-server\pom.xml
should also contain instructions on coping of Jasper reports.
<execution>
<id>copy-jasper-reports</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<outputDirectory>${staging.dir}/deployment/report_templates</outputDirectory>
<resources>
<resource>
<directory>${project.build.directory}/report_templates</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
Sometimes SVG pictures have to be used and rendered (see ElectricalSafetyCertificate.jrxml
from the TTGAMS project as an example). In this case Batik bridge is required to render SVG in JasperReports and should be introduced as a dependency in tgpsa-pojo-bl\pom.xml
.
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-bridge</artifactId>
<version>1.7</version>
</dependency>
To send generated PDF file as attachment please use one of the methods provided in the IEmailSender interface.
final File fileToAttach = new File(entity.getFileName());
// Write the binary data into the file
try (final FileOutputStream fos = new FileOutputStream(fileToAttach)) {
fos.write(entity.getData());
emailSender.sendPlainMessageWithAttachments(fromPersonEmail, toPersonEmail, subject, wholeMessage, fileToAttach.toPath()).flatMap(ex -> emailSendingErrorHandler.map(handler -> {
try {
handler.accept(invoiceHeader, ex);
return null;
} catch (final Exception e) {
return e;
}
}));
} catch (final IOException e) {
throw Result.asRuntime(e);
} finally {
fileToAttach.delete();
}
Jaspersoft iReport Designer 4.7.1 is rather old, but does run on macOS Mojave, however it seems that it needs an older version of Java (e.g. 7) and must be run manually.
Installation is the usual "drag to Applications directory" simple process.
Attempting to start it will undergo upgrade due to GateKeeper. Although you can right-click the .app file and manually open it, it will still undergo upgrade to start.
Attempting to start it from the command line results in:
Rowdys-iMac:tgpsa rowdy$ open /Applications/Jaspersoft\ iReport\ Designer.app
LSOpenURLsWithRole() failed with error -10810 for the file /Applications/Jaspersoft iReport Designer.app.
Attempting to run the actual executable results in:
Rowdys-iMac:MacOS rowdy$ /Applications/Jaspersoft\ iReport\ Designer.app/Contents/MacOS/ireport
Cannot find java. Please use the --jdkhome switch.
This is despite Java being correctly installed:
Rowdys-iMac:MacOS rowdy$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
Rowdys-iMac:MacOS rowdy$ which java
/usr/bin/java
Rowdys-iMac:MacOS rowdy$ java -version
Picked up JAVA_TOOL_OPTIONS: -Djava.awt.headless=true
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
Specifying the -jdkhome
option, as suggested above, results in the following:
Rowdys-iMac:MacOS rowdy$ /Applications/Jaspersoft\ iReport\ Designer.app/Contents/MacOS/ireport --jdkhome /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
Picked up JAVA_TOOL_OPTIONS: -Djava.awt.headless=true
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=512m; support was removed in 8.0
Ah, yes, we have JAVA_TOOL_OPTIONS set to headless due to issues attempting to run almost every other Java application.
Trying again without that variable set:
Rowdys-iMac:MacOS rowdy$ JAVA_TOOL_OPTIONS= /Applications/Jaspersoft\ iReport\ Designer.app/Contents/MacOS/ireport --jdkhome /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
Picked up JAVA_TOOL_OPTIONS:
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=512m; support was removed in 8.0
The application does attempt to start, displays a splash screen, but again undergoes upgrade and silently exits.
Trying again with an older version of Java:
Rowdys-iMac:MacOS rowdy$ JAVA_TOOL_OPTIONS= /Applications/Jaspersoft\ iReport\ Designer.app/Contents/MacOS/ireport --jdkhome /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home
Picked up JAVA_TOOL_OPTIONS:
And it actually seems to start and run.
Quitting the application raises the following exception:
2019-07-18 09:39:45.646 java[89711:13605019] org.netbeans.ExitSecurityException: Illegal attempt to exit early
at org.netbeans.TopSecurityManager.checkExitImpl(TopSecurityManager.java:192)
at org.netbeans.TopSecurityManager$PrivilegedCheck.run(TopSecurityManager.java:648)
at java.security.AccessController.doPrivileged(Native Method)
at org.netbeans.TopSecurityManager$PrivilegedCheck.check(TopSecurityManager.java:673)
at org.netbeans.TopSecurityManager$PrivilegedCheck.checkExit(TopSecurityManager.java:661)
at org.netbeans.TopSecurityManager.checkExit(TopSecurityManager.java:153)
at java.lang.Runtime.exit(Runtime.java:107)
at java.lang.System.exit(System.java:962)
at com.apple.eawt._AppEventHandler.performQuit(_AppEventHandler.java:145)
at com.apple.eawt.QuitResponse.performQuit(QuitResponse.java:51)
at com.apple.eawt._AppEventHandler$_QuitDispatcher.performDefaultAction(_AppEventHandler.java:390)
at com.apple.eawt._AppEventHandler$_AppEventDispatcher.dispatch(_AppEventHandler.java:512)
at com.apple.eawt._AppEventHandler.handleNativeNotification(_AppEventHandler.java:202)
For simplicity, a script was created and placed into a directory on the PATH as follows:
#!/usr/bin/env bash
JAVA_TOOL_OPTIONS= /Applications/Jaspersoft\ iReport\ Designer.app/Contents/MacOS/ireport --jdkhome /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home &
Now it starts, doesn't lock up the terminal while it is running, but still displays the stack trace upon exit. Seems like that's about as good as it will get.