So, I have installed Jaspersoft Studio as Eclipse plugin, and nice! It's integrated into my application, so I want to
create a java class to provide my data for a report. Do a google search and implement a custom JRDataSourceProvider.
Let's say, I implement my "RptMyCustomReport", give report fields and data and this works fine.
Next step, I want to filter my data based on report input parameter ... but I can't use my custom provider, I have to
use QueryExecuterFactory ... but now I loose my original class, or worse, I try to use JRDataSourceProvider (as provider for the report, useful to get report fields)
together with QueryExecuterFactory (as language for the report) with no luck.
What I wanted, was to give an interface to Jasper for implementing my report, an object that can provide any information to Jasper to build the report,
for example, "report field ?" "report input parameters?" "report data for these filters ?" but Jasper doesn't work in this mode.
So, to partially simulate that behavior, I found the solution below.
First, reports have these elements:
- a provider: this is used at design time to get report fields. It's not used a runtime.
- a query language to execute the query: this links the QueryExecuterFactory
- a query sql: the "only" data stored as is in the report
I implement the provider to get the reports fields to Jasper.
I put my "RptMyCustomReport" class in the query sql
I implement the custom QueryExecuterFactory to parse the sql as java class and create custom data at runtime
Here a basic example:
// this class shows only that you can pass a parameter to your costum data source
public class TestDataSourceFiltered implements JRDataSource { String[] fields; String[][] data; String sampleFilter; private Map<String, Integer> fieldIndex = new HashMap<>(); public TestDataSourceFiltered(String[] fields, String[][] data) { this.fields = fields; this.data = data; for( int i=0; i<fields.length; i++ ) fieldIndex.put(fields[i], i); } public void setParamFilters(Map<String, Object> params) { sampleFilter = (String) params.get("TEST_FILTER_PARAM"); } int pos = -1; @Override public boolean next() throws JRException { for(;;) { pos ++; if( pos >= data.length ) return false; String[] row = data[pos]; if( sampleFilter == null || row[0].equals(sampleFilter) ) return true; } } @Override public Object getFieldValue(JRField jrField) throws JRException { return data[pos][fieldIndex.get(jrField.getName())]; } }
// this is the provider:
// design time: get the report fields
// design / runtime: return null on data source, to pass the baton to QueryExecuterFactory
public class TestDataSourceProviderFiltered implements JRDataSourceProvider { public static class MyField extends JRBaseField { public MyField(String name, String desc, Class<?> clazz) { this.name = name; this.description = desc; this.valueClass = clazz; this.valueClassName = clazz.getName(); } } @Override public boolean supportsGetFieldsOperation() { return true; } // create java class from the query in the report private static TestDataSourceFiltered getInstanceFrom(JRReport report) { if( report == null || report.getQuery() == null ) return null; String s = report.getQuery().getText(); if( s.isEmpty() ) return null; try { return (TestDataSourceFiltered) Class.forName(s).newInstance(); } catch( InstantiationException | IllegalAccessException | ClassNotFoundException e ) { e.printStackTrace(); return null; } } @Override public JRField[] getFields(JasperReport report) throws JRException, UnsupportedOperationException { TestDataSourceFiltered ds = getInstanceFrom(report); if( ds == null ) return new JRField[0]; JRField[] res = new JRField[ds.fields.length]; for( int i=0; i<ds.fields.length; i++ ) { res[i] = new MyField(ds.fields[i], ds.fields[i], String.class); } return res; } // wanted: return null !! The data source will be provided by query executor factory @Override public JRDataSource create(JasperReport report) throws JRException { return null; } // used at design time and at run time public static JRDataSource createWithParams(JRReport report, Map<String, Object> params) throws JRException { TestDataSourceFiltered ds = getInstanceFrom(report); ds.setParamFilters(params); return ds; } @Override public void dispose(JRDataSource dataSource) throws JRException { } }
public class TestQueryExecuterFactoryFiltered extends AbstractQueryExecuterFactory { @Override public Object[] getBuiltinParameters() { return null; } Map<String, Object> unwrap(Map<String, ? extends JRValueParameter> in ) { HashMap<String, Object> res = new HashMap<>(); for( Entry<String, ? extends JRValueParameter> t: in.entrySet() ) { JRValueParameter value = t.getValue(); res.put( t.getKey(), value != null ? value.getValue() : null ); } return res; } @Override public JRQueryExecuter createQueryExecuter(JasperReportsContext jasperReportsContext, JRDataset dataset, Map<String, ? extends JRValueParameter> parameters) throws JRException { JRValueParameter paramReport = parameters.get(JRParameter.JASPER_REPORT); if( paramReport != null ) { JRReport jreport = (JRReport) paramReport.getValue(); if( jreport != null ) { return new JRQueryExecuter() { @Override public JRDataSource createDatasource() throws JRException { return TestDataSourceProviderFiltered.createWithParams( jreport, unwrap(parameters) ); } @Override public void close() { } @Override public boolean cancelQuery() throws JRException { return false; } }; } } throw new JRException("no provider for dataset: " + dataset.getName()); } @Override public boolean supportsQueryParameterType(String className) { return false; } }
How to use at design time:
1) register TestQueryExecuterFactoryFiltered in Eclipse
2) create a custom report using TestDataSourceProviderFiltered as provider
3) edit "query language" to point to TestQueryExecuterFactoryFiltered
4) create "RptMyCustomReport" derived from TestDataSourceFiltered
5) put in the sql editor the full qualified name of "RptMyCustomReport"
6) exit, save the report (little bug, until you save the report, JSS doesn't give you the query sql) and reopen, click on "read fields", and you get your report fields
How to use at runtime
public class MainTest { public static void main(String[] args) throws JRException { DefaultJasperReportsContext.getInstance().setProperty("net.sf.jasperreports.query.executer.factory.TEST", "test.TestQueryExecuterFactoryFiltered"); Map<String, Object> params = new HashMap<>(); params.put("TEST_FILTER_PARAM", "A"); File sourceFile = new File("./src/test/Blank_A4.jasper"); JasperReport jasperReport = (JasperReport)JRLoader.loadObject(sourceFile); JasperPrint jr = JasperFillManager.fillReport(jasperReport, params, TestDataSourceProviderFiltered.createWithParams(jasperReport, params)); JasperExportManager.exportReportToPdfFile(jr, "./test.pdf"); } }
Can someone explain points 4 and 5 ?