The reporting features in JRules Rule Studio build upon the services provided by the Eclipse Business Intelligence and Reporting Tools (BIRT) project. Out of the box we provide BIRT Data Sources to allow you to create customized reports using the BIRT WYSIWYG report designer. This is a good solution for the use cases where you need to make relatively straight-forward customizations to the JRules report templates.
I've spent a couple of days researching more advanced uses of BIRT with Rule Studio, in particular using BIRT Scripted Data Sources. A Scripted Data Source allows you to write Javascript to access all the features of the underlying Rule Studio API. In addition, because you are loosely-coupled to Rule Studio (relying just on the public API accessed via Javascript), you can use any version of BIRT that will run within the same Eclipse instance as Rule Studio, not necessarily a version that has been tested and is supported by ILOG.
Defining a BIRT Scripted Data Source
The first thing to do is to create your Data Source and assign it the type "Scripted Data Source". You can then create a BIRT Data Set for the Data Source and define the output columns you require.
For my example I defined a generic set of columns that are applicable for most rule artifacts.
Scripting the Data Set
Once you've defined the columns in your Data Set you have to write the glue-code to populate the columns using the Rule Studio API. The code is written using Javascript and you must implement two essential callback method on your Data Set. The "open" callback is called when BIRT first accesses your Data Set and is the place to put initialization code. The "fetch" callback is called to retrieve each row of data from the Data Set.
In my example the "open" function was very simple; it simply uses the Rule Studio API to return an iterator over all the Rule Artifacts within a Rule Project. The iterator is then accessed within the "fetch" function.
The "fetch" function is more interesting as it basically implements the Object-to-Column mapping from the Rule Studio POJO model to the columns defined in your Data Set. Note that you can access any variables defined in the "open" function within the "fetch" function! You return "false" from the "fetch" function to indicate that there are no more data rows to process.
The "fetch" method is straightforward, except for the population of the ruleDefinition Data Set column.
row["ruleDefinition"] = renderDefinitionAsHtml( brlRule );
The renderDefinitionAsHtml Javascript function performs the heavy-lifting to return an HTML version of a rule artifact. Note that for the example I only implemented support for Decision Tables and BAL rules.
Report Parameters
In my example report I require the name of a Rule Project to retrieve the Rule Artifacts. This can be conveniently passed using a BIRT report parameter. When you first execute a report BIRT will prompt for the values of report parameters. The value for a report parameter is retrieved within your Javascript using a global variable. E.g. params["ruleProjectName"].
Defining your Report
Defining your report is satisfyingly easy once you find your way around the BIRT user interface. You drag-and-drop report elements onto the report canvas using the Layout view. You can use Header/Footer and Grid elements to iterate on the elements of your Data Set.
Running your Report
Running your report is as easy as clicking on the "Preview" tab in the report editor. BIRT will prompt you for values for parameters if necessary and then render your report. This very interactive change cycle makes creating reports easy -- typically you just have to tweak the Javascript and then click on the "Preview" tab.
Best Practices
If you download my example you'll see that I've moved most of the Javascript into a separate file: "utils.js". This is convenient as it allows you to share utility functions across the "open" and "fetch" callbacks and makes it easy to use an Eclipse Javascript editor such as Adobe JSEclipse to edit utils.js. The only drawback is that you have to edit the report layout file by hand (using the XML view) to include utils.js.
You can also define a BIRT Library that defines the Scripted Data Source and the Data Set. This makes it easy to create multiple reports that use your Data Source.
Developing with Javascript takes patience! I recommend you make small changes to your code and test often. The error reporting from the Javascript engine used by BIRT (Rhino) can unfortunately be very cryptic. Although BIRT presents quite a steep and intimidating learning curve, in my experience the current version of BIRT seems solid and works as advertised.
Classloader Challenges
If you take a look at utils.js you will see rather ugly code like this:
if( isBrlRule( ruleArtifact ) ) { debug( "BRL Rule: " + ruleArtifact ); brlBundle = Packages.org.eclipse.core.runtime.Platform.getBundle( "ilog.rules.studio.model.brl" ); debug( "brlBundle: " + brlBundle ); brlServiceClass = brlBundle.loadClass( "ilog.rules.studio.model.brl.IlrBRLService" ); debug( "brlServiceClass: " + brlServiceClass ); signature = java.lang.reflect.Array.newInstance( java.lang.Class, 1 ); signature[0] = brlBundle.loadClass( "ilog.rules.studio.model.brl.IlrBRLRule" ); debug( "signature: " + signature ); method = brlServiceClass.getMethod( "getHTMLDefinition", signature ); debug( "method: " + method ); args = java.lang.reflect.Array.newInstance( java.lang.Object, 1 ); args[0] = ruleArtifact; return method.invoke( null, args ); }
As you can see, we are forced to retrieve the "root" Rule Studio classes using reflection. This is because the BIRT Report Viewer is actually implemented as a Servlet (WebApp) running within a Tomcat instance embedded within your Eclipse installation. So by default it does not have access to all the classes within its Eclipse host. There is BIRT documentation that describes how to deploy custom classes to the Report Viewer WebApp but I didn't like the sound of copying all the Rule Studio plugins within the BIRT Viewer and then dealing with potential ClassCastException problems. The reflection code works however, because even though the WebApp cannot see the Rule Studio classes directly, it does share the same OSGi Platform instance. You can therefore explicitly load the plugins you require and then use the loadClass method to load the classes. Once you've loaded the "root" object you can just use Javascript to call methods on the class and no longer need to use Java reflection APIs.
Conclusions
BIRT Scripted Data Sources allow you to use the powerful BIRT rendering engine to render any data you can retrieve using the public Rule Studio API. With careful design you can achieve good separation between the layout/view rendered using BIRT and the data coming from the Rule Studio model. You can use BIRT report libraries to share reusable Scripted Data Sources and utility Javascript functions across reports and team members. In addition the coupling between BIRT and Rule Studio is loose, giving you more flexibility in component versioning. Challenges with this approach include debugging Javascript and using Java reflection code to access the root instances of the Rule Studio model.
Download Code
RuleStudioReportingProject.zip
References
I found the following references useful while working on the example code. Many thanks to the respective authors for sharing their know-how!
- http://birtworld.blogspot.com/
- http://wiki.eclipse.org/index.php/BIRT/FAQ/Data_Access
- http://dev.eclipse.org/newslists/news.eclipse.birt/msg25632.html
- http://richclientplatform.blogspot.com/2007/07/birt-is-not-evil.html
- Scripted Data Source tutorial
- http://www.richclient2.eu/2006_11_17/eclipse-birt-a-cases-study/
- http://www.eclipse.org/birt/phoenix/examples/reports/
- http://www.mozilla.org/rhino/ScriptingJava.html