This is an update from someone who posted this very valuable information and I absolutely want to give all credits to this person, that I regrettably don’t know.
The original information however was becoming a little bit out of date and I tried to update it, and add some things I found usefull in setting up the integration between Ruby on Rails and JasperReports.
I’ve left the original text after my own text, so you can still read through the original text for additional information.
I’m not a native English speaker, so please excuse me if I made some errors against the English language. Feel free to correct my mistakes.
FlexRails.
JasperReports is a powerful—and even more important—well known open source Java reporting tool that has the ability to deliver rich content in formats such as PDF, RTF, HTML, CSV and XML. It is widely used and appreciated in the Java community because of its flexibility and the availability of various GUI tools for rapid report design.
For this example I will be using iReport.
The following paragraphs explain how to interface Rails and JasperReports.
The interface I’m describing on this page is based on a command line interface between rails and JasperReports. There is also a possibility to interface using webservices, but this is not in my scope yet.
These are the steps to succes (hopefully)
The data we will pass from rails to JasperReports will be in XML. XML is a standard datasource for JasperReports and rails is actually very good at generating XML data.
I have an Accounting controller. In this Accounting controller I created the following procedure to create a list of customers. I’ll show you the controller code, and the accompanying view.
The controller code : (<RAILSAPP>/app/controllers/accounting_controller.rb)
def customer_list @customers=Customer.find(:all) end
The view code :
(<RAILSAPP>/app/views/accounting/customer_list.rxml)
xml.instruct! xml.customer_list_result do xml.invoice_customers do @customers.each do |customer| xml << customer.to_xml(:dasherize=>false,:skip_instruct=>true,:only=>[:id,:name,:city],:root=>"customer") end end end
As you can see I fooled around a little bit with the rails to_xml method. You can keep it simpler off course but it is worth investigating the possibilities of to_xml.
Don’t you love how much you can do with a few lines of Ruby on Rails.
Now start your browser and goto http://localhost:3000/accounting/customer_list.
The xml result is now shown in your browser, and depending on your browser the output is in a pretty outline or not.
Anyway, goto the source of the page you are looking at. Every browser has this possibility, and you should now see your complete XML document. Copy-Paste it into your favorite text editor and save it somewhere on your disk. We will use this xml file for developing the report. Later on we will of course switch to a live rails data source and you can delete this file.
My XML file looks like this
<?xml version=“1.0” encoding=“UTF-8”?>
711 3D-Design Bigtown 496 A Nuino Smalltown 368 International Inc. Expensivetown
JasperReports is no more than a Java library that offers the ability to fill previously designed and compiled reports with some data and to save these reports in a specified file format.
I have installed this library in <RAILSAPP>/app/jasper.
If you put the runtime in this jasper directory and the reports you create in the reports directory, like I showed before.
The complete JasperReports will be deployed to your production server, just by deploying the <RAILSAPP>/app on the production server. There is nothing else to do on the production server. That’s the way I like it.
Okay, let’s go :
iReports has great support for adding barcodes to your reports. If you plan on using barcodes then you’ll also need these two jar files:
Actually, copying all files from <IREPORT>/lib to <RAILSAPP>/app/jasper/lib, is even better, because then all the features from iReport are available to your rails application.
It’s very well possible that for some files you will see other version numbers but the main thing is that you have to copy these files from your iReport lib into you rails app jasper lib. The reports you have created with your version have to stay in sync with the libraries from your iReport installation.
When generating a report in XLS format, I had a lot of grief with an error that said that it couldn’t find the field “IS_ONE_PAGE_PER_SHEET”. I was using jasperreports-3.0.0.jar. I’m not a Java guy so I can’t really figure out why, but the best I can tell, something changes since jasperreports-2.0.4.jar. The newer version of JasperReports doesn’t like the way that XmlJasperInterface.class sets this value. If you need version 3 as I did, you can:
javac XmlJasperInterface.java -cp “/rails/RAILS ROOT/app/jasper/lib/jasperreports-3.0.0.jar” -target 1.5
Also, XmlJasperInterface writes very helpful log file to the rails root directory, so make sure to look at that if you are having problems.
— Scotty
Your JasperReports runtime is now installed.
With the JasperReports runtime installed we need to add some rails code to make things work.
class Document
include Config
def self.generate_report(xml_data, report_design, output_type, select_criteria)
report_design << ‘.jasper’ if !report_design.match(/\.jasper$/)
interface_classpath=Dir.getwd+"/app/jasper/bin"
case CONFIG‘host’
when /mswin32/
mode = “w+b” #windows requires binary mode
Dir.foreach(Dir.getwd+"/app/jasper/lib") do |file|
interface_classpath << “;#{Dir.getwd}/app/jasper/lib/”file if (file != ‘.’ and file != ‘..’ and file.match(/.jar/))
end
else
mode = "w"
Dir.foreach(Dir.getwd+"/app/jasper/lib") do |file|
interface_classpath << “:#{Dir.getwd}/app/jasper/lib/”+file if (file != ‘.’ and file != ‘..’ and file.match(/.jar/))
end
end
result=nil
IO.popen “java -Djava.awt.headless=true -cp \”#{interface_classpath}\" XmlJasperInterface -o#{output_type} -f#{Dir.getwd}/app/reports/#{report_design} -x#{select_criteria}", mode do |pipe|
pipe.write xml_data
pipe.close_write
result = pipe.read
pipe.close
end
return result
end
end
module SendDocHelper
protected
def cache_hack
if request.env‘HTTP_USER_AGENT’ =~ /msie/i
headers‘Pragma’ = ‘’
headers[’Cache-Control’] = ‘’
else
headers[’Pragma’] = ‘no-cache’
headers‘Cache-Control’ = ‘no-cache, must-revalidate’
end
end
end
There you have it. The rails magic. Don’t ask to many questions.
One important thing to notice however, is that there are no calls to shell scripts or bat files. Rails makes a direct call to Java and communicates with the Java process through pipes. Although there is a little bit of performance loss in starting the Java process, the pipes based communication is very fast.
To run the report add this to a controller where you want to add an action for running the report :
helper :send_doc
include SendDocHelperdef customer_report
@customers=Customer.find(:all)
send_doc(
render_to_string(:template => ‘accounting/customer_list’, :layout => false),
‘/customer_list_result/invoice_customers/customer’,
‘custrep’,
‘CustomerReport’,
’pdf’)
end
The parameters to the send_doc method are :
Now open your browser again and go to :
http://localhost:3000/accounting/customer_report and a nice pdf should be displayed in your browser.
That’s it. I wish you all lots of success.
Invoking Jasper through a Ruby Java call does not always generate meaningful error messages.
To assist with resolving configuration issues, it may be helpful to manually invoke the call to Jasper from your command line.
This can be achieved using the following (or similar):
java -Djava.awt.headless=true -cp "C:/project/app/jasper/bin;C:/project/app/jasper/lib/commons-logging-1.0.2.jar;C:/project/app/jasper/lib/itext-1.3.1.jar;......" XmlJasperInterface -opdf -fC:/project/app/reports/CustomerReport.jasper -x/customer_list_result/invoice_customers/customer < customer.xml > custrep.pdf
Following you will find the original text before I started to mess around with it. It probably still contains usefull information for you to read/
JasperReports is no more than a Java library that offers the ability to fill previously designed and compiled reports with some data and to save these reports in a specified file format. Valid data sources are JDBC connections and XML files (using JRXmlDatasource), amongst others. Reading the data for the report from a database using raw SQL commands is of course possible but introduces duplicate logic, database vendor-specific code and concurrency issues. Besides that, there is no elegant way of reporting data that is not stored in a database but managed in Rails. There are no such problems if we use the XML data source and generate XML data in Rails using Builder templates—which is extremely easy and comfortable. For these reasons, the XML data source is used for feeding Rails data into JasperReports.
JasperReports is integrated into Rails by execution of a simple Java application that receives the XML data for the reports through an IO pipe, creates the report and passes the results back trough the pipe right into the Rails application.
The whole interface consists of the following parts:
This might seem like a lot of work but actually it is not. At least, if you don’t have to write all the code yourself ;).
I added a directory jasper to my rails application root. It contains three subfolders
Apart from that, I added a reports directory to the rails application root that contains the precompiled Jasper reports.
The rails code that builds the CLASSPATH, executes the Java app and handles the IO pipe looks like the following:
<pre>
As you can see from this code, there is no need to call any Windows batch file or Bash script. It has not been thoroughly tested but works in Windows and Linux (check that ‘java’ binary path though). Three parameters are passed to XmlJasperInterface:
The data returned by _generate_report_ is the final report that can be passed to a browser for example. Include the following module in every controller that needs to create and send reports:
<pre>
module SendDoc
protected
def cache_hack
if @request.env‘HTTP_USER_AGENT’ =~ /msie/i
@headers‘Pragma’ = ‘’
@headers[’Cache-Control’] = ‘’
else
@headers[’Pragma’] = ‘no-cache’
@headers‘Cache-Control’ = ‘no-cache, must-revalidate’
end
end
def send_doc(xml, xml_start_path, report, filename, output_type = ’pdf’)
case output_type
when ‘rtf’
extension = ‘rtf’
mime_type = ‘application/rtf’
jasper_type = ‘rtf’
else # pdf
extension = ‘pdf’
mime_type = ‘application/pdf’
jasper_type = ‘pdf’
end
cache_hack
send_data Document.generate_report(xml, report, jasper_type, xml_start_path),
:filename => “#{filename}.#{extension}”, :type => mime_type, :disposition => ‘inline’
end
end
The controller code could be as follows:
<pre>
end
In the above code we render the Builder template _xml_course_report_ into a string and pass this string to the send_doc method. _course_report_ is the filename of the precompiled report and course#{@course.id} is the filename of the PDF document that gets sent to the browser. The extension .pdf is added automatically in _send_doc_.
The Builder template (app/views/xml_course_report.rxml) could look as follows:
<pre>
xml.instruct!
xml.Course do
xml.MetaInformation do
xml.GeneratedAt(Time.now)
xml.GeneratedBy do
xml.Name(@user.name)
xml.UserName(@user.user_name)
xml.Email(@user.email)
end
end
xml.Information do
xml.Name(@course.name)
xml.Participants do
@course.participants.each do |p|
xml.Participant do
xml.Name(p.name)
xml.Phone(p.phone)
xml.City(:zip => p.city.zip, :name => p.city.name)
end
end
end
end
end
</pre>
VoilĂ , that’s it.
JasperReports itself is very fast, but bringing up a Java Virtual Machine for each call of _generate_doc_ takes its time (about 2 seconds on my machine). It would be much faster, if the Java interface application would run as a server application all the time and listening on a specific TCP port for incoming report generation requests. Then, the performance issue would be gone. I haven’t found the time to implement a JasperReport server yet. But, if somebody does, please let me know!
I think an option for better performance would be to use Apache Cocoon and call this pages from ruby. This way one could either use Apache FOP and XML/XSLT to generate PDF pages or integrate JasperReports or Eclipse BIRT into Cocoon. When Cocoon is then executed as Tomcat-Webapp the startuptime is nearly null. (markusw@nmmn.com)
What is quite interesting concerning the usage of JasperReports ist Jasper Intelligence, from http://jasperintel.sourceforge.net. They provide JasperReports as a Webservice.
RE:JasperReports as a Webservice. Although their website advertises support for PHP, Python and other scripting languages, this is not entirely true. Scouring through the Net as well as JasperSoft’s documentation does not yield any useful information about the server’s Web Service API. A quick look at their forums reveals that this isn’t fully implemented yet. A workaround to this would probably be to use WWW::Mechanize to get those document formats from the JasperServer. — relaxdiego
RE:RE:JasperReports as a Webservice.
There is a product available for this, and it’s called JasperIntelligence. Although the API is pretty much opaque, I was able to get a rails app to fetch PDF exports of the sample reports provided with JI via the web service. I have instructions on how it can be done in a basic way on the JasperIntelligence forum: http://www.jasperforge.org/index.php?option=com_joomlaboard&Itemid=215&func=view&id=15846&catid=10
I am definitely interested in more attention on this subject, since there don’t appear to be any enterprise class reporting solutions available for rails, and Crystal Reports is well outside of my means ($18k+ per year). +-MariusAgricola
RE:RE:RE Jasper Reports as a webservice
I’m using the following setup:
Jetty server (java) providing a jython interface to jasper reports
You call the jasper jetty service from rails like so :
options = {
:c_sql_where=>@grid.search_arg_sql(params) || ‘1=1’,
:c_sql_order=>@grid.order_arg_sql(params) ,
:outputType=>params[:outputType],
:compiledDesign=>’customer_equipments’,
}
send_jasper_export(:mysql,options,’equipments’)
send_jasper_export is a helper method that does the actual call by :
response = Net::HTTP.post_form(URI.parse(‘http://jasper:8080/jasapp/jasper.py’),arguments)
This works great and the performance is very good.
You can build the report with ireport and use a xml databasource or a sql datasource. All the c_ arguments are customer arguments that are passed as parameters to the report.
If there is interest i could write a bigger tutorial, contact me at daniel[AT]itxl.nl
I’m looking into getting this working using Jasper reports compiled to use a JDBC data source. If anyone has already done this or can see a trivial way to do it then please share here.
You can download the Java interface application, the shell script as well as the JasperReports library 1.1.0 (and the required 3rd-party libraries) bundled from this location.
Notice however, that this Wiki-Page gets updated by people around the world whereas the code you can download here does not and there might be differences between the code shown above and the one you download. For example, I originally used a shell script between Rails and Java, but apparently somebody updated this Wiki-Page with a much more elegant approach that calls the Java Virtual Machine directly from Rails.
Including the JasperReport source file for this sample data would make this much easier to understand. —It is included now.