Update October 27th, 2007: I have written a more recent entry that should make life a lot easier.
I’m sure most of us have come across
Jasper Reports at some stage. Jasper gives Java developers a complete reporting infrastructure so dynamic reports can be generated from databases in a pretty, printer friendly format.
It has many similarities to the infamous Crystal Reports. It is however, completely open source and designed from the ground up to be flexible, extendable and valuable.
In short: it rocks.
So, how do we get this cool functionality with PHP? Well the obvious solution is to do it all in Java and use the good ole exec (or similar) to create the report and throw the output back to the browser. There are a couple of issues with this however that make it undesirable:
- It is slow. Every time a report is generated we have to wait for the Java virtual machine to start, execute the code and unload again. Not the best.
- Passing parameters between PHP and Jasper becomes troublesome. You will have to pass things either through the exec command, or use something like popen so you can communicate to and from Java though standard in / out.
Thankfully we can have PHP talk to Java efficiently through the excellent work of the guys at PHP/Java Bridge.
The bridge works by having a Java “server” running in the background. Communication is done between the VM and PHP through a light weight network protocol. This approach has a number of advantages including stability (something that the normal PHP extension has failed to achieve) and it neatly address our two major concerns above.
All is not rosy however. The PHP/Java Bridge comes with its own set of challenges, especially when dealing with Jasper.
- The current directory of the JVM (or “user.dir”) is unknown.
Jasper has a funny way of achieving things. First, it takes a nicely formatted XML file as input and converts this into a temporary Java source file. It then compiles this .java file through javac. After that it loads it up, passes any params to it and gets it to create a report that can be exported to a variety of formats. Sounds complicated but this approach really makes things more flexible.
We run into problems here when jasper comes to creating the java source representation of the XML .jrxml file. By default, jasper will create the temp file in the current working directory. Unfortunately this is usually root (/), which the JVM has no write access to (thankfully) so it dies a horrible screaming death.
This can be overridden by setting the System property “jasper.reports.compile.temp”, however this does not appear to work when setting it through PHP.
- Classpaths are all screwy. I actually have 1 installation of the bridge that by all rights should not be working as the required Jars are not in the classpath of the JVM at all. The issues, unfortunately, are usually the other way around.
This issue raises its head once the java file is created and now needs to be compiled. For some reason, any Jars that are added to the classpath through the java_require command are not available to the javac call when jasper wants to go ahead and compile the java source file.
Once again, there is a system property that can be used as the class path for Jasper, this is “jasper.reports.compile.class.path”. However, as I mentioned above, setting this in PHP does not work.
- Relative directories are a no-no.
Because PHP and java are running separately, each has their own “working” directory. We cannot simply pass java a file and expect it to know what path it is in. Use absolute paths for everything in Java.
Fortunately there are a couple of solutions for the above. The first is altering the way PHP creates a JVM. By modifying the java options in php.ini we can make PHP connect to the java server through a TCP socket. We can then start the java server through a simple java command with the appropriate class path and user.dir settings passed through the command line. The drawback to this approach is that it means we have to start the java server separately to the web server, and the whole thing will come crashing down around us if we start apache without java running.
An alternative is to write another java server based on the standard one that comes with the bridge to have the environment setup the way we want. The problem with this approach is that we are lazy and time is money.
The other option that I thought of and the one I’ve gone with is creating a little wrapper class to set the appropriate system properties before creating the Jasper report.
And here it is: download
To use, compile and package it up into a .jar file for use by the bridge.
This class takes away most of the pain in creating a jasper report. It provides utility functions for retrieving the parameters a report requires and exporting reports to the default supported formats. It also manages a cache of the reports so they don’t have to be recompiled each time they are run, which is the most expensive part of the whole process.
We can then use the following code to run a report. Remember that $javaParams is a Java HashMap of java objects, not a PHP array:
Or this to get a listing of all the params a report requires. Note that I had to convert between the java HashMap and the PHP associative array:
Now, we have to make sure we pass java values back as parameters to the runReport function above. Thankfully there are not that many java types we can use as Jasper parameters and most of them take strings in one form or another. You may find this function useful for converting between PHP and Java values:
Okay, now a few words of wisdom to finish up this little post to help get you to Jasper Reports nirvana: