Apache Cayenne > Index > Cayenne Examples > Jasper Integration
Description
JasperReports is a reporting platform that typically executes SQL directly on the database to obtain results. However, with minimal effort, query execution can be routed through and managed by Cayenne. Very little code is required to actually integrate the two, so I demonstrate this through code snippets without any attached code.
JRDataSource
Jasper reads results through a JRDataSource implementation, which basically behaves as a guided HashMap iterator. The default implementation, JRResultSetDataSource, works directly with JDBC ResultSets. However, it is easy to create an implementation for Cayenne's DataRows:
public class DataRowDataSource implements JRDataSource { private Iterator rowIterator; private DataRow activeRow; public DataRowDataSource(List dataRowList) { rowIterator = dataRowList.iterator(); activeRow = rowIterator.next(); } public boolean next() throws JRException { boolean hasNext = rowIterator.hasNext(); if (hasNext) { activeRow = (DataRow) rowIterator.next(); } return hasNext; } public Object getFieldValue(JRField field) throws JRException { return activeRow.get(field.getName()); } }
DataRequestor
Because a JR report definition file entirely describes a report, it makes sense that the report itself should determine how the data is fetched. For example, a report based on log files should probably not be routed through Cayenne. Thus, our integration should not prevent this. To allow the report to control its execution, add a property called "dataRequestor". In this example, the value of dataRequestor will refer to a Spring bean, but could easily be a fully qualified class name. DataRequstor will have a simple interface:
public interface DataRequestor { public static final String DATAREQUESTOR_PROPERTY = "dataRequestor"; JRDataSource execute(JasperReport report, Map parameters) throws JRException; }
Essentially, it will read the report definition and return the appropriate JRDataSource.
CayenneDataRequestor
The CayenneDataRequestor, then, will be implemented as follows:
public class CayenneDataRequestor implements DataRequestor { public JRDataSource execute(JasperReport report, Map parameters) throws JRException { DataContext dataContext = DataContext.getThreadContext(); String queryName = report.getQuery().getText(); SQLTemplate query = (SQLTemplate) dataContext.getEntityResolver().lookupQuery(queryName); List resultSet = dataContext.performQuery(query.queryWithParameters(parameters)); return new DataRowDataSource(resultSet); } }
As you can see, the query text is treated as a name for a SQLTemplate. Thus, where you would normally provide SQL in the report designer, instead provide a SQLTemplate (or any SelectQuery) name. You can easily create a JDBC-based data requestor to execute the query and wrap the results in a JRResultSetDataSource.
Report Execution
To actually invoke the DataRequestor, use the following snippet:
JasperReport report = (JasperReport) JRLoader.loadObject(definitionFile);
DataRequestor dataRequestor = (DataRequestor) applicationContext.getBean(report
.getProperty(DataRequestor.DATAREQUESTOR_PROPERTY));
JRDataSource dataSource = dataRequestor.execute(report, parameters);
JasperPrint jasperPrint = JasperFillManager.fillReport(report, parameters, dataSource);
Note that definitionFile is an InputStream, File, URL or String-based location to the compiled JR definition file. You can also use the JasperCompileManager to pass in an uncompiled definition file. The jasperPrint object is ready to be passed to a JRExport implementation, such as JRPdfExporter.