Codion uses a plugin oriented approach to report viewing and provides an implementation for JasperReports.
With the Codion JasperReports plugin you can either design your report based on an SQL query in which case you use the JRReport class, which facilitates the report being filled using the active database connection, or you can design your report around the JRDataSource implementation provided by the JasperReportsDataSource class, which is constructed around an iterator.
1. JDBC Reports
Using a report based on an SQL query is the simplest way of viewing a report using Codion, just add a method similar to the one below to a EntityTablePanel subclass. You can then create an action calling that method and put it in for example the table popup menu as described in the adding a print action section.
public class CustomerTablePanel extends EntityTablePanel {
public CustomerTablePanel(SwingEntityTableModel tableModel) {
super(tableModel);
// associate a custom Control with the PRINT control key,
// which calls the viewCustomerReport method in this class,
// enabled only when the selection is not empty
control(PRINT).set(Control.builder()
.command(this::viewCustomerReport)
.caption("Customer report")
.icon(FrameworkIcons.instance().print())
.enabled(tableModel().selection().empty().not())
.build());
}
private void viewCustomerReport() {
List<Entity> selectedCustomers = tableModel().selection().items().get();
if (selectedCustomers.isEmpty()) {
return;
}
Collection<String> customerIds = Entity.values(Customer.ID, selectedCustomers);
Map<String, Object> reportParameters = new HashMap<>();
reportParameters.put("CUSTOMER_IDS", customerIds);
JasperPrint customerReport = tableModel().connection()
.report(Customer.REPORT, reportParameters);
Dialogs.builder()
.component(new JRViewer(customerReport))
.owner(this)
.modal(false)
.title("Customer Report")
.size(new Dimension(800, 600))
.show();
}
}
2. Export
A report fills to a JasperPrint by default, which the receiver needs JasperReports on its classpath to read. Wrapping the report in an export via JasperReports.export() changes what filling it produces, a PDF for example, in which case nothing of JasperReports reaches the receiver.
The export runs wherever the report is filled, which for a remote connection is on the server, so only the exported document crosses the wire. This is what makes reports available to a client which can not host the reporting engine, an Android or a web client for example.
The export is bound where the report is registered, so the report type carries the result type and the caller need not ask for a format:
// Fills to a JasperPrint, which the receiver needs JasperReports to read
ReportType<Map<String, Object>, JasperPrint> REPORT =
reportType("customer_report");
// Fills to a PDF, which the receiver needs nothing at all to read
ReportType<Map<String, Object>, byte[]> PDF_REPORT =
reportType("customer_pdf_report");
JRReport<JasperPrint> customerReport =
classPathReport(Reports.class, "customer_report.jasper");
add(Customer.REPORT, customerReport);
// The export runs where the report is filled, on the server for a remote
// connection, so only the PDF crosses the wire. Both report types share
// the one loaded report and its cache
add(Customer.PDF_REPORT, export(customerReport, PDF));
JasperPrint print = connection.report(Customer.REPORT, reportParameters);
byte[] pdf = connection.report(Customer.PDF_REPORT, reportParameters);
JRExport provides PRINT, PDF and XML, any other JasperReports exporter being a lambda.
|
Note
|
JRExport.PDF requires the net.sf.jasperreports:jasperreports-pdf artifact, which is not a dependency of the plugin.
It registers itself through a jasperreports_extension.properties resource scanned off the classpath, so nothing requires its module, and it is neither resolved on the module path nor included in a jlink image unless named explicitly, via --add-modules net.sf.jasperreports.pdf.
|
3. JRDataSource Reports
The JRDataSource implementation provided by the JasperReportsDataSource simply iterates through the iterator received via the constructor and retrieves the field values from the underlying entities. The easiest way to make this work is to design the report using field names that correspond to the attribute names, so using the Store domain example from above the fields in a report showing the available items would have to be named 'name', 'active', 'category_code' etc.
EntityConnection connection = connectionProvider.connection();
EntityDefinition customerDefinition =
connection.entities().definition(Customer.TYPE);
Iterator<Entity> customerIterator =
connection.select(all(Customer.TYPE)).iterator();
JasperReportsDataSource<Entity> dataSource =
new JasperReportsDataSource<>(customerIterator,
(entity, reportField) ->
entity.get(customerDefinition.attributes().getOrThrow(reportField.getName())));
JRReport<JasperPrint> customerReport = fileReport("reports/customer.jasper");
JasperPrint jasperPrint = JasperReports.fillReport(customerReport, dataSource);