Monday, November 24, 2008

Moving

I'm moving this blog to http://blog.darevay.com. Bye blogger.

Thursday, November 1, 2007

Scripted Data Sources with BIRT

I recently started experimenting with the BIRT reporting engine for Java. It seems pretty powerful, well-designed, and has a nice report design tool implemented in Eclipse. Since the project I'm working on doesn't have a database, I decided to use BIRT's scripting data sources. These allow you to create a (hopefully) small JavaScript glue layer between the reporting engine and your plain old Java objects. The purpose of this post is to cover some of the gotchas I've encountered so far.

The basic idea is that you implement a few JavaScript callback methods that feed row-like back to the reporting engine. BIRT uses Mozilla's Rhino JavaScript engine. Since the typical use case for BIRT is a database backend, the documentation for the scripting interface seems harder to come by. The FAQ helped, but didn't answer all of my questions.

First, all of the examples retrieve their "model" from a static factory method called from JavaScript. This isn't that realistic. How do I pass my model in to the JavaScript? Or at least a parameter like a filename? I think part of the reason all the examples take this approach is so that it's possible to run the report at design time with the preview window. I did some digging and eventually found the right calls:
   MyModel model = ...;
IRunAndRenderTask task = engine.createRunAndRenderTask(design);
Map parameters = new HashMap();
parameters.put("model", model);
task.setParameterValues(parameters);
Cool. Now my model's being passed to the engine. But wait. How does BIRT find my classes? I know it's built on top of Eclipse which means the classpath is not as simple as you'd like. In some examples I found, you have to manually copy your compiled class files to a directory in one of the BIRT plugins. Uggh. This is a pain, but acceptable for deployment. But during development? I might as well go back to C++ if I want to waste that much time.

Some more searching dug up this FAQ question. It lists too yucky solutions and then has a tacked on snippet of code that looks promising.
   config = new EngineConfig();
HashMap hm = config.getAppContext();
hm.put(EngineConstants.APPCONTEXT_CLASSLOADER_KEY,MyClass.class.getClassLoader());
config.setAppContext(hm);
However, I guess I think this is for the version of BIRT I'm not using because it doesn't work. Some more digging and I come up with this:
   IRunAndRenderTask task = engine.createRunAndRenderTask(design);
HashMap contextMap = new HashMap();
contextMap.put(EngineConstants.APPCONTEXT_CLASSLOADER_KEY,
MyModel.class.getClassLoader());
task.setAppContext(contextMap);
I guess the app context stuff moved from the task to the engine at some point. Anyway, now we can finally try it out. I set up my open callback in Java and... wait, how do I actually get to my model in the JavaScript code? This wasn't too tricky to figure out. The parameters passed to the engine above are passed to the JavaScript code in a global (?) parameter aptly named params:
    model = params["model"];
Nice. Now I write some more code to call some methods on the model and run it and... NullPointerException. This is where I was stuck for a little while. I thought that I had screwed up the class loader stuff or something so I fiddled with that stuff for a while. Eventually, when I was about to give up, I stumbled upon this bug report which, although it doesn't mention a NullPointerException, does mention a "display name" which I had noticed references to while poking around in the debugger after my crash.

The trick is that the params variable in JavaScript doesn't hold just my model, but an extra wrapper object with a display name and value is stuck in there. I revise my code to look like this:
    model = params["model"].value
and voila, I run it again and my report is generated as expected.

Hope this is helpful to someone else out there.