Thursday, October 26, 2006

BIRT: Scripted Data Source to Call a Web Service

One of the things that makes BIRT a powerful platform is its how extendable it is due to the ability to invoke Java classes. In the case of the following example, I can report off of the previously developed Web Service using Apache Axis (or more specifically, the WSDL2JAVA utility), a Java class, and a scripted data source.

The first thing I need to do is create the proxy classes using Apache Axis. I used Axis 1.4 for this example since 1.2 seemed to create classes that I couldn’t develop against using Java 1.5. The web service resides at http://my.web.server/test/WS/getEmployeeTranscript/Service1.asmx, and since it is a .Net web service, I will need to pull the WSDL by calling http://my.web.server/test/WS/getEmployeeTranscript/Service1.asmx?WSDL. In order to generate the proxy classes using Axis, I had to call WSDL through Java to generate the WSDL file. Although not necessary, I included all of the JAR files under the <Axis Folder>/lib. So to create my classes, I run the following command:

java -classpath "C:\apache_axis\axis-1_4\lib\axis-ant.jar;C:\apache_axis\axis-1_4\lib\axis.jar;C:\apache_axis\axis-1_4\lib\commons-discovery-0.2.jar;C:\apache_axis\axis-1_4\lib\commons-logging-1.0.4.jar;C:\apache_axis\axis-1_4\lib\jaxrpc.jar;C:\apache_axis\axis-1_4\lib\log4j-1.2.8.jar;C:\apache_axis\axis-1_4\lib\saaj.jar;C:\apache_axis\axis-1_4\lib\wsdl4j-1.5.1.jar" org.apache.axis.wsdl.WSDL2Java http://my.web.server/test/WS/getEmployeeTranscript/Service1.asmx?WSDL

A pretty long command, a batch file or something will help in future uses. I have seen wrappers for this before, but apparently Apache did away with including one in the distribution in a previous version. Anyway, I get a set of classes creating in the namespace structure of my Web Service.

Now that I have these, I need to create a small class to handle my calls. The reason I do this is for logical reasons. Techincally, I could just import these proxy classes and invoke them directly from BIRT, however, I don’t feel that they logically make sense. In my mind, I have a process like so invisioned:

Invoke class with Employee ID ( Assign Data Set values the property values of my Class.

That simple, I don’t want to deal with the issues of calling the Locator class generated from Axis inside of BIRT. So I will create a very simple Java class that will call the web service in its constructor method, and have get methods to return the employees name, ID, and transcript information.

The class I create was done in Eclipse, including my generated Axis classes in the buildpath. It is as follows:

//I have no idea how the java.* and javax.* classes got imported??
import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
import my.webservice.namespace.*;

public class pojoTest {
//Local variables to store the returned information
private String name;
private String geid;
private TranscriptEntry[] trans;

//The constructor, which will invoke the web service on invokation
public pojoTest(String id)
{
//Get the locator class for my web service
GetEmployeeTranscriptLocator getEmp = new GetEmployeeTranscriptLocator();

try {
//Create the employee object, and assign the variables the results
Employee emp = getEmp.getGetEmployeeTranscriptSoap().getEmployeeAndTranscript(id);
geid = emp.getID();
name = emp.getName();
trans = emp.getTranscript();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServiceException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

//My get methods for my properties
public String getName()
{
return name;
}

public String getID()
{
return geid;
}

public TranscriptEntry[] getTranscript()
{
return trans;
}

//Main will test the class from the command line
public static void main(String[] args) {
// TODO Auto-generated method stub
pojoTest p = new pojoTest("0005003335");

System.out.println(p.getID() + " - " + p.getName());
for (int x = 0; x &lt; p.getTranscript().length; x++)
System.out.print(p.getTranscript()[x].getCourseCode() + " - " + p.getTranscript()[x].getCourseName() + '\n');
p = null;

}

}


I do a test run and check my console output to verify that the web service is being called correctly. Once I am satisfied, I gather up the Axis JAR files, and my .CLASS file for my created class. Now, I read this article on BIRTWorld which seemed to indicate that I could just keep the class file in my project directory and have this work. I will be perfectly honest, I was not able to get that to work. I will have to ask Scott next time I talk to him how he got that to work. What I ended up having to do is follow the steps from here. (Sorry, apparently Actuate doesn’t believe in Anchor tags, so just look for the question “Q: How do I get data from a POJO (Plain Old Java Object)? “). I copied my compiled .CLASS file, my Apache Axis JAR files, and the proxy classes (keeping the same namespace) into a folder under the following path:

C:\Eclipse\eclipse\plugins\org.eclipse.birt.report.viewer_<version>\birt\WEB-INF\classes

Now, since this is all set up for previewing, I need to create a report that consumes this as a scripted data source. In the BIRT Perspective, I create a new report project, called pojoTest. To create the scripted data source, first, go to the Data Sources slot in either the Data Explorer or in the Outline, right mouse click, and choose New Data Source. In the Wizard, choose “Scripted Data Source” and give it a name.

Now, as an aside, I am not sure that the Scripted Data Source is really “doing” anything behind the scenes, I am visualizing that it is just a placeholder, and empty object that fufills the requirement of a dataset that it must have a data source. I could be wrong about that, however I haven’t seen anything that would lead me to believe otherwise, but once I accepted this, I was able to wrap my head around the process much easier.

With the data source created, I go to the Data Sets slot, right mouse click on it, and create a new Data Set. Most of the information is already filled in, I just change the name of the data set to something more meaningful. It is already set to the scripted data source. The columns I created for my data set are as follows:

ID
Name
CourseCode
CourseName

With the data set selected, change to the Script tab in the Report Designer, and select the Open method for the Data Set. To get the scripted data set to call the object, I used the following code in the Open Method:

obj = Packages.pojoTest("0005003335");
transcript = obj.getTranscript();
cnt = 0;
max_cnt = transcript.length;

Here, the class I created is being called via the Packages.pojoTest call. I then pull the transcript into a separate object. The cnt variable is being used in the fetch method as a placeholder to determine which record we are currently on, and the max_cnt will be used to loop through each transcript entry.

Now, the Fetch method itself, where our data set returns a row, will keep being called until it returns false. So, what I do is keep incrementing cnt, set the values of my row, and return true. If cnt > max_cnt, then return false. The Fetch method is below:

if (cnt < max_cnt)
{
row["ID"] = obj.getID();
row["Name"] = obj.getName();
row["CourseCode"] = transcript[cnt].getCourseCode();
row["CourseName"] = transcript[cnt].getCourseName();
cnt++;

return true;
}
else
return false;

Now I can use the data set like a regular query data set. I create a table element in my report, and run the report. It works like butter.

1 comment:

gaurav said...

hi all!

I m new to this BIRT tool and i dont have any knowldge of java but i know a little bit of SQL.

Now i have to make a report template for my tool EMC IONIX. I am able to generate the report but i want to see the limited no. of rows like if i want to see the rows from some date to some other date.
These start date or end date i want to use when i run the report, means as the run lime variable where i'll manually put the dates as per my requirement whnever i wud need the result from the report.

i saw an option of applying filter while creating the the DATA SET but may i use the run time parameters there, if yes thn how??

Please help me...