How to Integrate JasperReports with Ruby on Rails

Introduction

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.

Interface

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:

  • Jasper Reports library
  • Java interface application that takes XML data and produces reports
  • Shell script to invoke the Java application
  • Rails code that builds an appropriate CLASSPATH, executes the Java interface application, and reads/writes through an IO pipe
  • Rails code that generates the XML data (using Builder templates)
  • Some rails code that sends the generated report to the browser or stores it somewhere

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 ;).

Code

I added a directory jasper to my rails application root. It contains three subfolders

  • bin – the Java class file of the interface application
  • src – the Java source file of the interface application
  • lib – the Jasper Reports library and every 3rd-party library that is needed

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 fAs:[  
class Document b�Y>Ug{O;  
  include Config 6]Jv3Re'(I  
  def self.generate_report(xml_data, report_design, output_type, select_criteria) 	CqoL5qt	  
    report_design << '.jasper' if !report_design.match(/\.jasper$/) C$bK!]a  
    interface_classpath=Dir.getwd+"/jasper/bin"  Z|W=.RdA;  
    case CONFIG['host'] *%O1d.,  
      when /mswin32/ r
(uM$R$o  
        Dir.foreach("jasper/lib") do |file| B$bsh.  
          interface_classpath << ";#{Dir.getwd}/jasper/lib/"+file if (file != '.' and file != '..' and file.match(/.jar/)) !rWib`%  
        end d
>%_<pw  
      else 1Jahu!c?  
        Dir.foreach("jasper/lib") do |file| 2d._X$fx7  
          interface_classpath << ":#{Dir.getwd}/jasper/lib/"+file if (file != '.' and file != '..' and file.match(/.jar/)) sJr$[?  
        end {b?)|@)is  
    end 
@.})nU  
    pipe = IO.popen "java -cp \"#{interface_classpath}\" XmlJasperInterface -o#{output_type} -freports/#{report_design} -x#{select_criteria}", "w+b"  eN$~@'w  
    pipe.write xml_data gFKQm(0g2  
    pipe.close_write y?rsfIth`  
    result = pipe.read yR`-rJb V  
    pipe.close l-	X|3�,  
    result u&qdrKx  
  end _uBf.Qfs  
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:

  • -o the report output type (supported types are pdf, rtf, xml, xls and csv; even though I only really tested pdf and rtf)
  • -f the path and filename of the precompiled report
  • -x an XML select path (refer to Using the XML datasource for further information)

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"  #NqA5QR  
  PXkPC%j  
module SendDoc D1w_Vpz  
  protected $]Q_x?  
  def cache_hack 
&pCKz[Yf+  
    if @request.env['HTTP_USER_AGENT'] =~ /msie/i q%JV"9,  
      @headers['Pragma'] = '' toQn]MT  
      @headers['Cache-Control'] = '' ZFRKh:|  
    else 5'o.v^l  
      @headers['Pragma'] = 'no-cache' ^)
SvH		  
      @headers['Cache-Control'] = 'no-cache, must-revalidate' pZH
bj2~  
    end R	~cc]kp0  
  end N+r~\[N\9  
  
wHAh6lm  
  def send_doc(xml, xml_start_path, report, filename, output_type = 'pdf') _-D(N/  
    case output_type -<|Ebh	d3  
    when 'rtf' rf9RG!  
      extension = 'rtf' bk8IGhO|m!  
      mime_type = 'application/rtf' `dj/Uk  
      jasper_type = 'rtf' sG\=_-"v(  
    else # pdf s#d>yx_b  
      extension = 'pdf' ~zuMX;[  
      mime_type = 'application/pdf' MHgS5b2  
      jasper_type = 'pdf' 0AaN	  
    end $mpfr#!&3o  
  [%b<%m}L-  
    cache_hack wPV`j:?'  
    send_data Document.generate_report(xml, report, jasper_type, xml_start_path), 
D$j`+`  
        :filename => "#{filename}.#{extension}", :type => mime_type, :disposition => 'inline' Q,s,EooIx  
  end :
\.v\.wm  
end

The controller code could be as follows:

class ReportController < ApplicationController $+
?6U  
  include SendDoc ntj
Und&v\  
  zis-}K<  
  def report <q_H 3|  
    @course = Course.find(params[:id]) @m(\f  
    @user = User.find(session[:user_id]) |ejrE,~1vb  
    send_doc(render_to_string(:template => 'xml_course_report', :layout => false), YrB-;R1+  
      '/Courses', 'course_report', "course#{@course.id}", 'pdf') ~kYF/B2*  
  end U;;Har  
  6_9w1
,WE  
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! S?D2`b  
xml.Course do 8y+Gvk:  
  xml.MetaInformation do t[3Upe%  
    xml.GeneratedAt(Time.now) {VI%]n{M  
    xml.GeneratedBy do 
I2zSoQ1P  
      xml.Name(@user.name) :S,#*rPKBK  
      xml.UserName(@user.user_name) ^ 41p+  
      xml.Email(@user.email) gAR];(*  
    end n)pBK>+  
  end RPLr7Lb  
  xml.Information do xv1$,|^ts  
    xml.Name(@course.name) `UeF3~)>E  
    xml.Participants do !WDdq_n*v  
      @course.participants.each do |p| {\	.2h  
        xml.Participant do 4u:{PN  
          xml.Name(p.name) 
GZO,]%z  
          xml.Phone(p.phone) #Xsby  
          xml.City(:zip => p.city.zip, :name => p.city.name) p|W:;(  
        end NbgP,-  
      end 7O"T`>  
    end S$V'_  
  end @Wdnc/o]  
end

Voilà, that’s it.

Performance Issues

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

Download

You can download the Java interface application, the shell script as well as the required 3rd-party libraries (including XmlJasperInterface.class) bundled here.

Discussion

Jeff Dean, 2009/06/24 21:27

Comment pulled from doc: “Including the JasperReport source file for this sample data would make this much easier to understand.”

 
Jmini, 2009/08/06 08:18

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

你可能感兴趣的:(xml,PHP,webservice,Ruby,Rails)