Friday, February 5, 2010

JasperReports in Spring with runtime report and format

This post is after a while. I recently finished a phpCake project, which I want to write about a bit, but in an another post. This post is about something I recently was challenged with and came up with a pretty decent solution. (I think..) hope it helps.

My current project is a Spring based solution, built on Spring MVC, spring Security, Sitemesh, JPA with Hibernate and all other bells and whistles. It also integrates JasperReports. Spring has built in support for JasperReports. You can see it documented (that can be debated) here. It has built in View implementations for different renderings of the JasperReports. (csv, html, pdf, xls).

It works, with a little bit of digging.

But it was not sufficient for my requirement, which was:
Allow for rendering of a jasper report picked at runtime in a format chosen at runtime.

The View resolvers pretty much expect that the report url be defined at configuration time. Spring has another View implementation (JasperReportsMultiFormatView) that allows run time format choice, but the JasperReport url still needs to be defined at configuration time.

I understand where this behavior comes from, as majority of the time the reports are built and packaged with source code and the rendering is wired. But it fell short of allowing picking JasperReport at runtime. So, determined to get it to work within the framework of Spring Jasper View implementations (with its nifty support for both .jrxml and compiled .jasper) , I came up with this solution. Of course, this is one implementation, and can be optimized.. If you do, please drop in a note in comments.

1. Create a JasperReportsViewFactory

protected static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";

public AbstractJasperReportsSingleFormatView getJasperReportsView(HttpServletRequest httpServletRequest,
DataSource dataSource, String url, String format, String fileName){
String viewFormat = format==null?"pdf":format;

// set possible content headers
Properties availableHeaders = new Properties();
availableHeaders.put("html", "inline; filename="+fileName+".html");
availableHeaders.put("csv", "inline; filename="+fileName+".csv");
availableHeaders.put("pdf", "inline; filename="+fileName+".pdf");
availableHeaders.put("xls", "inline; filename="+fileName+".xls");

// get jasperView class based on the format supplied
// defaults to pdf
AbstractJasperReportsSingleFormatView jasperView = null;
if(viewFormat.equals("csv")) {
jasperView = new JasperReportsCsvView();
}else if(viewFormat.equals("html")){
jasperView = new JasperReportsHtmlView();
}else if(viewFormat.equals("xls")){
jasperView = new JasperReportsXlsView();
}else{
jasperView = new JasperReportsPdfView();
}

// get appContext. required by the view
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(
httpServletRequest.getSession().getServletContext());

// set the appropriate content disposition header.
Properties headers = new Properties();
headers.put(HEADER_CONTENT_DISPOSITION, availableHeaders.get(viewFormat));

// set the relevant jasperView properties
jasperView.setJdbcDataSource(dataSource);
jasperView.setUrl(url);
jasperView.setApplicationContext(ctx);
jasperView.setHeaders(headers);

// return view
return jasperView;
}

As you can see, the return type is a super class of all the Spring's Jasper View implementations. Another feature in this method is to set content disposition and supply an alternate file name for the downloaded file.

2. Use It
Once you have this , make it a dependency in your controller responsible for launching the report, and provide the url, format and other required elements for the view resolver, and execute your report.
public ModelAndView handleRequest(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {

Map model = new HashMap();

String format = httpServletRequest.getParameter("format");
//Default format to pdf
if (StringUtils.hasText(format)){
if (!(format.equalsIgnoreCase("pdf") || format.equalsIgnoreCase("html")
|| format.equalsIgnoreCase("csv") || format.equalsIgnoreCase("xls"))){
format = "pdf";
}
}else{
format = "pdf";
}

// get the View that will render the report.
// in actual controller, based on report id rquestd, get the report name and build URL and use
AbstractJasperReportsSingleFormatView jasperView = jasperReportsViewFactory.getJasperReportsView(
httpServletRequest, dataSource, "/WEB-INF/jasper/sample.jrxml",format,"DownloadFileName");

// add parameters used by the report
model.put("Company","Sample Company");
// more here...

return new ModelAndView(jasperView, model);
}


Limitations:
I'm sure there are some.. One that I know of is Jasper's sub-reports. For me it is not an issue right now as we are not supporting Jasper sub-Reports in our application.

There you have it.. short and sweet. Here are some other links if this is not what you're looking for.

Matt Raible's blog on JasperReports with Appfuse and Spring (dated)
AppFuseJasperReports
A springOne presentation of Spring and JasperReports
Filename discussion using JasperReportsMultiFormatView using configuration

3 comments:

  1. definately helped me. Great job!

    ReplyDelete
  2. with sub reports, it works perfectly. Thanks...

    ReplyDelete
  3. Yes .. its well work with sub-reports. If you configure it with spring, you have to create the same bean in xml and while using the bean in java code assign it to new and then set all the required properties will work.

    ReplyDelete