Friday, October 15, 2010

BIRT Java Object Data Type

In addition to simple data types BIRT also supports a Java Object Data type. This type can be useful when writing reports that use scripted data sources that consume POJOs that contain a member object. For example assume you have an array of objects and each of these objects contains three member variables (a String, an Integer, and an Object).




Before BIRT supported the Java Object data type you could write a scripted data source that iterated the array and assigned a row variable for the string member and another for the integer variable. If you wanted to use the Object member you would need to call some method on the object that returned a BIRT primitive type. While this is generally not a drawback with a simple table, if you wanted to nest tables and use the inner object in another dataset, scripting this became very complex. Using the Java Object data type now makes this much easier. The only caveat to using the Java Object data type is that the object must be serializable. The reason for this is that BIRT caches datasets and the object will be cached just like any other column value.

To illustrate using the Java Object type, consider the following classes. The BIRTOuterJavaObject contains string, integer, and object member variables.



package test.birt.javaobject;

import java.io.Serializable;

public class BIRTOuterJavaObject implements Serializable {


private static final long serialVersionUID = 6530113212317618087L;
private String outerstring;
private int outerint;
private BIRTInnerJavaObject innerobj;

public String getOuterstring() {
return outerstring;
}

public void setOuterstring(String outerstring) {
this.outerstring = outerstring;
}

public int getOuterint() {
return outerint;
}

public void setOuterint(int outerint) {
this.outerint = outerint;
}

public BIRTInnerJavaObject getInnerobj() {
return innerobj;
}

public void setInnerobj(BIRTInnerJavaObject innerobj) {
this.innerobj = innerobj;
}

public BIRTOuterJavaObject() {

}
}



The BIRTInnerJavaObject is just a sample object that stores information about an automobile. It contains three member variables which are all strings.



package test.birt.javaobject;

import java.io.Serializable;

public class BIRTInnerJavaObject implements Serializable {

private static final long serialVersionUID = -4509250036928470517L;


private String make;
private String model;
private String year;
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public BIRTInnerJavaObject() {
this.year = "2009";
this.make = "Chevrolet";
this.model = "Corvette";
}
public String toString(){
return "Make:--"+this.make+" Model:--"+this.model+" Year:--"+this.year;
}
}


The BIRTRow class sets up the array of objects that will be used in the report.


package test.birt.javaobject;

import java.util.ArrayList;

public class BIRTRow {

private ArrayList rows = null;

public BIRTRow() {
rows = new ArrayList();
rows.clear();
}
public void setupRows(){

BIRTOuterJavaObject row1o = new BIRTOuterJavaObject();
BIRTOuterJavaObject row2o = new BIRTOuterJavaObject();
BIRTOuterJavaObject row3o = new BIRTOuterJavaObject();

BIRTInnerJavaObject row1i = new BIRTInnerJavaObject();
BIRTInnerJavaObject row2i = new BIRTInnerJavaObject();
BIRTInnerJavaObject row3i = new BIRTInnerJavaObject();


row1i.setMake("Toyota");
row1i.setModel("Land Cuiser");
row1i.setYear("2008");
row1o.setOuterint(1);
row1o.setOuterstring("Outer Row 1 String");
row1o.setInnerobj(row1i);
rows.add(row1o);

row2i.setMake("Jeep");
row2i.setModel("Cherokee");
row2i.setYear("2002");
row2o.setOuterint(2);
row2o.setOuterstring("Outer Row 2 String");
row2o.setInnerobj(row2i);
rows.add(row2o);

row3i.setMake("Land Rover");
row3i.setModel("LR3");
row3i.setYear("2006");
row3o.setOuterint(3);
row3o.setOuterstring("Outer Row 3 String");
row3o.setInnerobj(row3i);
rows.add(row3o);

}
public ArrayList getRows(){
return rows;
}
}



The primary goal of the example is to nest two tables. The outer table will have three columns. One column for each of the member variables of the outer object(BIRTOuterJavaObject). The Inner BIRT table will contain three columns as well, one for each of the member variables of the inner object(BIRTInnerJavaObject). Note that in this example the inner object could have contained a list of objects but for simplicity it only contains one object. Because the inner dataset will only return one row of data the example could have been completed using just one data set. So the report will contain two datasets one for the outer object and one for the inner object. The inner object dataset will be parameterized so that when we nest the inner table it can get the object from the outer dataset. The inner dataset will only return one row of data.



The outer dataset scripted data set code is as follows:
Open Method:



importPackage(Packages.test.birt.javaobject);
myrows = new BIRTRow();
myrows.setupRows();
rowsiter = myrows.getRows().iterator();
icnt = 0;


The open method sets up the array of objects and gets an iterator for the list.
Fetch Method:


if( rowsiter.hasNext() )
{
var obj = rowsiter.next();
row["OuterString"] = obj.getOuterstring();
row["OuterInt"] = obj.getOuterint();
row["InnerObj"] = obj.getInnerobj();
icnt++;
return true;
} else {
return false;
}


The fetch method iterates over each of the outer objects and assigns the column values. Note that the InnerObj column is of type Java Object and will be an instance of BIRTInnerJavaObject.


The inner dataset has three string columns.



The inner dataset also has a parameter that we will set when the table is nested. The parameter is named JavaObjectParam and its default value is set to null.



The inner data set code is as follows:

Open method:


importPackage(Packages.test.birt.javaobject);
if( inputParams["JavaObjectParam"] == null ){
icnt=1;
}else{
icnt=0;
}



The open method just checks to see if the dataset parameter is set. If the parameter is null the dataset will not return any rows. If the parameter is set the dataset will only return one row which is an instance of BIRTInnerJavaObject.

Fetch Method:


if( icnt > 0 )return false
var obj = inputParams["JavaObjectParam"];
row["Make"] = obj.getMake();
row["Model"] = obj.getModel();
row["Year"] = obj.getYear();
icnt++;
return true;



In the fetch method we get the object from the parameter and call the getter methods for the object and assign them to the column values.

Once both datasets are created, you can drag the outer dataset to the report canvas which will create the first table. Next add a column to the first table and drag the second dataset to the new column which should create the second (nested) table. To make the inner table work properly the dataset parameter has to be bound to the outer tables InnerObj column. To do this select the inner table and click the binding tab in the properties view. Click the data set parameter binding button and set the value of the dataset parameter to row[“InnerObj”].



The Java Object Data type can also be consumed by a chart. Continuing with this example if you add a chart and bind it to the outer dataset you can specify getter methods for the series, categories, optional grouping, tooltip help, etc.



The completed report should have similar output to the following.



This example is available at BIRT-Exchange as two projects. One Java project and one BIRT Report project. To run the example import both projects to your workspace, open the BirtJavaObjectReport.rptdesign and preview the report.

If you are using Actuate BIRT 11, The Java Object data type can be used in conjunction with the new POJO ODA data source that allows the Report Designer to access and report off of POJO’s using a GUI designer. If you are interested in this approach take a look at this blog post and this video that show the driver in action. An example of using this new data source is available on BIRT-Exchange and you can try out Acutate BIRT 11 by downloading it here.

13 comments:

Jhon Davis said...

thank you for helping me out!

Anonymous said...

Hi Jason, nice article.

I wanted to ask a quastion about the dependency between the two data set boundings but then I saw that you do that in the table itself via data set parameter.
Didn't saw that at the first moment, maybe because I expected to see that earlier in this article.

Cheers,
Kosta

Jason Weathersby said...

Kosta,

What is your question?

Jason

Unknown said...

If I am not using scripting but I want to use ScriptedDataSetEventAdapter then how can I implement same example ?
Thanks In advance .

Jason Weathersby said...

I am not 100% on what the issue is. If you want to set a Java Object type in Java, just make sure the column type in designer is set to Java Object and in your fetch use:

row.setColumnValue("colname", youjavaobject);

Nele said...

Is it possible to use a Scripted DataSet with a column typed "Java Object" directly in a Data Component?

E.g.
In the fetch script:
row["myObj"] = myJavaObject;
Expression in the Data Component:
dataSetRow["myObj"].myProp
or
dataSetRow["myObj"].getMyProp()

Kind Regards,

Nele

Jason Weathersby said...

yes this is possible. As a simple test create a scripted data set with open:
ii=0;
importPackage(Packages.java.util);
mta = new ArrayList();
mta.add("one");
mta.add("two");
mta.add("three");
and fetch:
if( ii > 0 )return false;
row["tst"] = mta;
ii++;
return true;

Then use the following expression in a data item bound to the scripted data set:
dataSetRow["tst"].size();

Unknown said...

I have similar type of report design but instead of inner java object I am using List.. I have followed all the steps as per this blog but still not able to generate list in inner table. In out table I am getting list like [Str1,Str2,Str2]
Please let me know how to get this list in inner table.

inspirationalmusic said...

Can u provide the source code?

Pipebaum said...

Code for the entire project can be found at:
https://github.com/eclipse/birt

The specific code for the BIRT data object can be found at:
https://github.com/eclipse/birt/tree/master/data/org.eclipse.birt.data.oda.pojo

Nouman said...

Hi,
I followed the above steps and was able to communicate between two nested tables but unable to do so incase of three. I have posted my query in this link. Any help in this regard is highly appreciated.
https://stackoverflow.com/questions/58047248/birt-reporting-unable-to-bind-inner-table-parameter-to-outer-table-data-row-va

Thanks in Advance

Rozysharma said...

In Java, it is not possible to create an array of int values. The reason for this is that there are only 8 bits available in a byte. If you need to store a bigger number than 8 bits, you will have to convert your int value into an array of bytes and then back into an int again.The following example convert int to array in java of bytes
java int to int array
int number = 110101;
String temp = Integer.toString(number);
int[] numbers = new int[temp.length()];
for (int i = 0; i < temp.length(); i++) {
numbers[i] = temp.charAt(i) - '0';
}
convert int to array in java
public class ArraySum {
public static void main(String[] args) {
int first = Integer.parseInt(args[0]);
int[] firstArray = transform(first);
int second = Integer.parseInt(args[1]);
int[] secondArray = transform(second);
int[] result = sum(firstArray, secondArray);
print(result);
}

public static int[] transform(int num){
// TODO change only the following part.

}

public static int[] sum(int[] a, int[] b){
// TODO change only the following part.

}

public static void print(int[] array){
System.out.print("[");
for (int i = 0; i < array.length; i++){
if(i != array.length - 1) {
System.out.print(array[i] + ", ");
}else{
System.out.print(array[i] + "]");
}
}
}

}

Surya said...



Thank you for this blog. It is very useful. Share more like this.

JAVA Training in Chennai
JAVA Course in Chennai