Restored from http://web.archive.org/web/20061020013144/http://wiki.rubyonrails.com/rails/pages/HowtoIntegrateJasperReports
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.
The following paragraphs explain how to interface Rails and JasperReports.
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 connecti***** 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 reas*****, 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 c*****ists 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:
# document.rb 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+"/jasper/bin" case CONFIG['host'] when /mswin32/ Dir.foreach("jasper/lib") do |file| interface_classpath << ";#{Dir.getwd}/jasper/lib/"+file if (file != '.' and file != '..' and file.match(/.jar/)) end else Dir.foreach("jasper/lib") do |file| interface_classpath << ":#{Dir.getwd}/jasper/lib/"+file if (file != '.' and file != '..' and file.match(/.jar/)) end end pipe = IO.popen "java -cp \"#{interface_classpath}\" XmlJasperInterface -o#{output_type} -freports/#{report_design} -x#{select_criteria}", "w+b" pipe.write xml_data pipe.close_write result = pipe.read pipe.close result end end
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:
require "document.rb" 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:
class ReportController < ApplicationController include SendDoc def report @course = Course.find(params[:id]) @user = User.find(session[:user_id]) send_doc(render_to_string(:template => 'xml_course_report', :layout => false), '/Courses', 'course_report', "course#{@course.id}", 'pdf') end 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 could look as follows:
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
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. ([email protected])
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
You can download the Java interface application, the shell script as well as the required 3rd-party libraries (including XmlJasperInterface.class) bundled here.
Restored from http://web.archive.org/web/20061020013144/http://wiki.rubyonrails.com/rails/pages/HowtoIntegrateJasperReports
Comment pulled from doc: “Including the JasperReport source file for this sample data would make this much easier to understand.”
The mentioned Jasper Intelligence project is integrated to JasperServer (http://jasperforge.org/projects/jasperserver or http://jasperforge.org/projects/jasperserver)
A JasperServer running on Tomcat (for example) is able to serve compiled report through SOAP queries. This enable you to integrate JasperReport in “PHP, Python and other scripting languages”.
Example of PHP integration is provided in the JasperServer distribution in the samples/ directory http://jasperforge.org/scm/viewvc.php/branches/js-3.5.0/samples/php-sample/?root=jasperserver Or for newer PHP version attached to this ticket: http://jasperforge.org/plugins/mantis/view.php?id=4038
I have seen people using the same approach with Ruby. (browse the forum, I think there is also an example somewhere)
Documentation for this SOAP request can be found here: http://jasperforge.org/espdocs/docsbrowse.php?id=74&type=docs&group_id=112&fid=305