This section will demonstrate a couple of ways to use the results of a Hibernate query as the datasource of a report in JasperReports.
When the Hibernate query returns a simple collection of objects, The JRBeanCollectionDataSource class in the JasperReports library will work fine.
List cats = session.find("from eg.Cat"); Map parameters = new HashMap(); parameters.put("Title", "The Cat Report"); InputStream reportStream = this.class.getResourceAsStream("/the-cat-report.xml"); JasperDesign jasperDesign = JasperManager.loadXmlDesign(reportStream); JasperReport jasperReport = JasperManager.compileReport(jasperDesign); JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(cats); JasperPrint jasperPrint = JasperManager.fillReport(jasperReport, parameters, ds); JasperManager.printReportToPdfFile(jasperPrint, "the-cat-report.pdf");
However, when the Hibernate query returns tuples of objects (each tuple as an array, each array as an element in the returned List), things get a little tricky. Jasper needs a way to reference each object in the array by a field name. This class is a good solution, but at this time you are required to pass an array of field names matching the results of the query.
public class HibernateQueryResultDataSource implements JRDataSource { private String[] fields; private Iterator iterator; private Object currentValue; public HibernateQueryResultDataSource(List list, String[] fields) { this.fields = fields; this.iterator = list.iterator(); } public Object getFieldValue(JRField field) throws JRException { Object value = null; int index = getFieldIndex(field.getName()); if (index > -1) { Object[] values = (Object[])currentValue; value = values[index]; } return value; } public boolean next() throws JRException { currentValue = iterator.hasNext() ? iterator.next() : null; return (currentValue != null); } private int getFieldIndex(String field) { int index = -1; for (int i = 0; i < fields.length; i++) { if (fields[i].equals(field)) { index = i; break; } } return index; } }
Now running your report would look something like this:
List cats = session.find("select cat.type, cat.birthdate, cat.name from eg.DomesticCat cat"); Map parameters = new HashMap(); parameters.put("Title", "The Cat Report"); InputStream reportStream = this.class.getResourceAsStream("/the-cat-report.xml"); JasperDesign jasperDesign = JasperManager.loadXmlDesign(reportStream); JasperReport jasperReport = JasperManager.compileReport(jasperDesign); String[] fields = new String[] { "type", "birthdate", "name"}; HibernateQueryResultDataSource ds = new HibernateQueryResultDataSource(cats, fields); JasperPrint jasperPrint = JasperManager.fillReport(jasperReport, parameters, ds); JasperManager.printReportToPdfFile(jasperPrint, "the-cat-report.pdf");
Another, alternate implementation, from Erik Romson:
public class HibernateQueryResultDataSource implements JRDataSource { private static final transient Logger logger = Logger.Factory.getInstance(HibernateQueryResultDataSource.class); protected HashMap fieldsToIdxMap=new HashMap(); protected Iterator iterator; protected Object currentValue; List values; public HibernateQueryResultDataSource(List list, String query) { int start =query.indexOf("select "); int stop =query.indexOf(" from "); Assertion.assertTrue( (start!=-1) && (stop!=-1), "The query "+ query+ " must be of the form select x,x from ..." ); start+="select".length(); String parameters=query.substring(start,stop); parameters=parameters.trim(); Assertion.assertTrue( parameters.length()>0, "The query "+ query+ " seems to be weird" ); StringTokenizer tokenizer=new StringTokenizer(parameters,","); int idx=0; while( tokenizer.hasMoreTokens() ) { String parameter=tokenizer.nextToken(); fieldsToIdxMap.put( parameter.trim(), new Integer(idx) ); idx++; } values=list; this.iterator = list.iterator(); } public Object getFieldValue(JRField field) throws JRException { String fieldName=field.getName().replace('_','.'); //JasperReports does not allow '.' in field names, so we //use '_' for nested properties instead and must convert here //(comment added by kbrown) Integer idxInt=(Integer) fieldsToIdxMap.get(fieldName); if (idxInt==null) { Assertion.fail( "The field \""+ fieldName+ "\" did not have an index mapping. Should be one off "+ fieldsToIdxMap.keySet() ); } Object[] values = (Object[]) currentValue; Assertion.assertTrue( idxInt.intValue()<values.length, "The index from field "+ fieldName+ " was illegal" ); Object value= values[ idxInt.intValue() ]; if ( logger.isDebugEnabled() ) { logger.debug("fetched value "+value+" from field "+fieldName); } return value; } public boolean next() throws JRException { currentValue = iterator.hasNext() ? iterator.next() : null; return currentValue != null; } public List getValues() { return values; } }
Using the information from this page I've made a patch to JasperReports 0.6.5. You can find the patch at: http://www.waltermourao.com.br/JasperReports/jr65_hql.zip or you can download the full version from: http://www.waltermourao.com.br/JasperReports/jasperreports-hql-0.6.5.zip
Comments