A Generic JRDataSource for JasperReports

By Kenan Sevindik

It is possible to pass data to JasperReports templates via a custom data source, which implements the JRDataSource interface. The reporting engine iterates over the report data collection with the boolean next() method and evaluates field expressions by requesting values for them from the data source instance using the Object getFieldValue(JRField) method. One most probably implements custom data sources by returning some property value of the current data record corresponding to a field name passed from the template via the reporting engine.

We have developed a concrete JRDataSource class, which takes a list as a record data collection in its constructor and then returns field values, extracting them via reflection from the current record data in the collection if there is a one-to-one correspondence between that record’s properties and field expressions in the report template. There is no restriction on the depth of the object structure while extracting field values. For example, we may have an object named A as the current record data, but may ask a field value which corresponds to some property of an object named B, which is also a property of the previous object named A. The sole requirement for this idiom to work is implementing getter methods for those properties mentioned above.

Below is a simple usage scenario. Let’s say we have two classes, Foo and Bar, as follows;

public class Foo {
    private float f;
    private Bar bar;
    public Bar getBar() {
        return bar;
    }

    public float getF() {
        return f;
    }
    ...
}

public class Bar {
    private int i;
    private String str;
    public int getI() {
        return i;
    }

    public String getStr() {
        return str;
    }
    ...
}

Later, we can define field expressions such as, assuming an instance of class Foo is the top-level report data record, bar.str, bar.i, f, and access them with $F{bar.str}, $F{bar.i}, ${f} expressions respectively inside report templates.

Here is the source code of this custom data source idiom;

public class ListJRDataSource implements JRDataSource {
    private Iterator iterator;
    private Object currentObject;
    public ListJRDataSource(List objectList) {
        iterator = objectList.iterator();
    }

    public boolean next() throws JRException {
        if(iterator.hasNext()) {
            currentObject = iterator.next();
            return true;
        } else {
            return false;
        }
    }

    public Object getFieldValue(JRField jrField) throws JRException {
        String fieldName = jrField.getName();
        return FieldValueGetter.getValue(currentObject,fieldName);
    }
}

public class FieldValueGetter {
    public static Object getValue(Object target, String property) {
        int index = property.indexOf(".");
        if(index != -1) {
            target = getFieldValue(target,property.substring(0,index));
            return target != null?getValue(target,property.substring(index + 1)):null;
        } else {
            return target != null?getFieldValue(target,property):null;
        }
    }

    private static Object getFieldValue(Object target, String property) {
        try {
            property = convertToGetter(property);
            Method method = target.getClass().getMethod(property,null);
            return method.invoke(target,null);
        } catch (Exception e) {
            throw new FieldValueAccessException(e.getMessage(),e);
        }
    }

    private static String convertToGetter(String property) {
        StringBuffer buf = new StringBuffer();
        buf.append("get");
        buf.append(property.substring(0,1).toUpperCase(Locale.US));
        buf.append(property.substring(1));
        return buf.toString();
    }
}
Share: X (Twitter) LinkedIn