How to create and use a JRDataSource adapter

Sometimes it is necessary to retrieve information from sources unsupported by any data adapters. In this case you can write your own java class to connect and query these sources, and then elaborate the extracted information to make them compatible with JasperReport. To do this you must define a custom data adapter specifying its class that implements the JRDataSource interface and some other information. Doing this, JasperReport will use this class to extract the information and will use it to fill the report.

In this example will see how to create a custom JRDataSource adapter. The code in this example will be pretty simple, because all the information provided by the data adapter is hardcoded in the adapter itself. This means that the information provided is embedded and for this reason there is no need of other resources. This is a simplistic approach to show how to create a custom data adapter without creating too complex an example.

The Class of the Custom Data Adapter

The first thing to do is decide which data is returned and how it is structured. Let's suppose that we have a series of records where every record is a name and an age. So we need to return this information. Now we need to understand how a data adapter is composed. A custom data adapter is a piece of java code with some characteristics:

Keep in mind that these methods are called for every record. At first the method next is called and if its result is true then the getFieldValue is called for every field in the report. Then the method next is called again and the cycle will be repeated until it returns false.

Now that the concept behind this custom data adapter is explained, we can see the code (the code is also attached to this tutorial):

package CustomDataAdapter; import java.util.HashMap; import net.sf.jasperreports.engine.JRDataSource; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRField; public class MyImplementation implements JRDataSource < /** * For this data adapter the informations are embedded in the code */ private static final String[] nameArray = ; private static final Integer[] ageArray = ; /** * Variable to store how much records were read */ private int counter = -1; /** * Variables to store the number of fields, and their names, in the report */ private HashMap fieldsNumber = new HashMap(); /** * Method used to know if there are records to read. */ private int lastFieldsAdded = 0; @Override public boolean next() throws JRException < if (counterreturn false; > /** * This method is called for every field defined in the report. So if i have 2 fields it is called 2 times for every record, and * for each of them it must provide a value. * The parameter can be used to understand for which field is requested, in fact it contains the name of the requested field. This * data adapter is done with the goal of return two values, a name and an age. An easy option would be expect a field with the name * "Name" and one with name "Age". But we can do something more flexible, in this case we will enumerate all the fields requested and * and the first two will be assumed to be for name and age, for all the others will be returned an empty string. So we can have a report * with more than two fields, but the name and the age will be returned each time only for the first two. * * If this example is too much complex refer to the method getFieldValue2, where is shown the first explained, and simple solution, where we * expect two fields with a precise name. */ @Override public Object getFieldValue(JRField jrField) throws JRException < Integer fieldIndex; if (fieldsNumber.containsKey(jrField.getName())) fieldIndex = fieldsNumber.get(jrField.getName()); else < fieldsNumber.put(jrField.getName(), lastFieldsAdded); fieldIndex = lastFieldsAdded; lastFieldsAdded ++; >if (fieldIndex == 0) return nameArray[counter]; else if (fieldIndex == 1) return ageArray[counter]; return ""; > /** * Example of a simpler getFieldValue, not actually used */ public Object getFieldValue2(JRField jrField) throws JRException < if (jrField.getName().equals("Name")) return nameArray[counter]; else if (jrField.getName().equals("Age")) return ageArray[counter]; return ""; >/** * Return an instance of the class that implements the custom data adapter. */ public static JRDataSource getDataSource() < return new MyImplementation(); >>

Location of the Class

At this point we need only to understand where put this class and how use it. From the designer, right click on a JasperReport project folder and select New -> Package (if you don't see the element package search it under others), then use the name CustomDataAdapter for the new package.

1(23).png.2800c1ca57e4ef1509feef5aadfb6720.png

At this point right click on the CustomDataAdapter package and select New -> Class. On the dialog that will appear insert MyImplementation as class name. Then press the button Add to add a new interface and select JRDataSource, if you have done right the JRDataSource interface will appear in the list of the used interfaces. Finally hit the Finish button to create the new class. At this point you have to write the data adapter code, but for this example you can just copy and paste the code written before.

2(20).png.79355b81463954ea04287206a1bb13b5.png

Creation of the Data Adapter

Now you have to create the data adapter that use this class, from select the element File -> New -> Data adapter.

3(14).png.618c364cdca69f868c84bfa3584f656f.png

From the dialog select the same project folder where you put the class (in this case MyReports) and has name of the file put MyCustomAdapter and hit Next. Now you have to choose the type of the data adapter, select Custom Implementation of JRDataSource and hit Next. At this point you must provide the information to get the class previously written:

After you have compiled all the fields hit the Test button to check if it is all right, and if you obtain a successful response hit the Finish button.

4(15).png.bc8830cc9001984e3aecde71bf993502.png

Creation of the Report

At this point you can use this new data adapter inside a report. Create a new report from the report wizard (File -> New -> Jasper Report), for this example use the template Coffee. Place it in the same Project folder of the data adapter (MyReports) and as data adapter select MyDataAdapter and hit Finish. Now create two fields (In the outline view right click on the element Fields and select Create Field, one time for each field), and drag and drop them in the detail band. Then you have to change the Class property of the second field to java.lang.Integer, since by default it is String, but our data adapter return a string for the first value and an integer for the second one. If you don't do this then you will get a ClassCastException from JasperReports when you will run the report, becuase it is not able to do this conversion automatically. Probably you will have to adjust the size of the band and of the frame inside it (you could also remove this frame). Anyway ad the and you will obtein something like this:

5(12).png.199004de465d763b535eae6888cd5bf5.png

If you don't have the Automatic Build of your projects enabled (and by default it is disabled), you need to build your class file. To do this simply right-click on your project and select the option "Build Prjoect", if there aren't errors then you can proceed with the preview.

NOTE: If after building your project you still get ClassNotFound exeption when executing the report try to close and reopen Jaspersoft Studio and rebuild the project, because in some rare cases it could be possible that the Java Classloader is not able to load correctly a just created class.

build.png.287260600e5cdae77c93d9ece77194f9.png

Finally switch to the Preview tab to compile the report and see the result: