Hot questions for Using JasperReports in spring mvc

Question:

I've been investigating the use of JasperReports (6.0.0) with Spring MVC (4.1.3) to generate PDF reports. Spring is rife with "Spring specific" ways to integrate with JasperReports to generate PDFs:

I struggled to find good, complete examples online and wanted to share my findings (see my answer below).

Feel free to add additional methods and/or improvements related to "How can I integrate JasperReports with Spring4"?


Answer:

Based on my research, I've found the following usage methods. The methods begin with the most direct (naive) approach involving less up front complexity / configuration and evolve to become more abstract but with more dependencies on Spring / more complex Spring configuration.

Method 1: Use the JasperReports API directly in the Controller

Just write out the content to the servlet output stream.

  @RequestMapping(value = "helloReport1", method = RequestMethod.GET)
  @ResponseBody
  public void getRpt1(HttpServletResponse response) throws JRException, IOException {
    InputStream jasperStream = this.getClass().getResourceAsStream("/jasperreports/HelloWorld1.jasper");
    Map<String,Object> params = new HashMap<>();
    JasperReport jasperReport = (JasperReport) JRLoader.loadObject(jasperStream);
    JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, params, new JREmptyDataSource());

    response.setContentType("application/x-pdf");
    response.setHeader("Content-disposition", "inline; filename=helloWorldReport.pdf");

    final OutputStream outStream = response.getOutputStream();
    JasperExportManager.exportReportToPdfStream(jasperPrint, outStream);
  }

Method 2: Inject JasperReportPdf View into Controller

Given the JasperReportsPdfView bean:

@Bean @Qualifier("helloWorldReport2")
public JasperReportsPdfView getHelloWorldReport() {
  JasperReportsPdfView v = new JasperReportsPdfView();
  v.setUrl("classpath:jasperreports/HelloWorld2.jasper");
  v.setReportDataKey("datasource");
  return v;
}

This view can be injected or wired into the Controller for use:

@Autowired @Qualifier("helloWorldReport2")
private JasperReportsPdfView helloReport;

@RequestMapping(value = "helloReport2", method = RequestMethod.GET)
public ModelAndView getRpt2(ModelAndView modelAndView) {
  Map<String, Object> parameterMap = new HashMap<>();
  parameterMap.put("datasource", new JREmptyDataSource());
  modelAndView = new ModelAndView(helloReport, parameterMap);
  return modelAndView;
}

Note that using the JasperReportsPdfView (or the more versatile JasperReportsMultiFormatView) requires a dependency on spring-context-support:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
  <version>4.1.3</version>
</dependency>

Method 3: Use XML or ResourceBundle view resolver to map logical view names to JasperReport views

Configure a new view resolver, in this case the ResourceBundleViewResolver to run before the InternalResourceViewResolver. This is based on the order values being set (0 happens before 1):

@Bean
public ResourceBundleViewResolver getResourceBundleViewResolver() {
  ResourceBundleViewResolver resolver = new ResourceBundleViewResolver();
  resolver.setBasename("jasperreport-views");
  resolver.setOrder(0);
  return resolver;
}

@Bean
public InternalResourceViewResolver getInternalResourceViewResolver() {
  InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  resolver.setPrefix("/WEB-INF/views/");
  resolver.setSuffix(".jsp");
  resolver.setOrder(1);
  return resolver;
}

Then, at the root of our classpath, the jasperreport-views.properties file can contain the logical view name paired with the class and property values (i.e. url and reportDataKey) pertinent to rending a JasperReport:

helloReport3.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
helloReport3.url=classpath:/jasperreports/HelloWorld3.jasper
helloReport3.reportDataKey=myDataSourceKey

The controller code looks like this:

@RequestMapping(value = "helloReport3", method = RequestMethod.GET)
public ModelAndView getRpt3(ModelMap modelMap, ModelAndView modelAndView) {
  modelMap.put("myDataSourceKey", new JREmptyDataSource());
  return new ModelAndView("helloReport3", modelMap);
}

I like this approach. Controllers stay "dumb" and only deal with String values and the mapping of names to views can happen all in one location.


Method 4: Use JasperReportsViewResolver

Configure a zero-ordered JasperReportViewResolver and the trick is use setViewNames to tell Spring which logical view names you want this resolver to deal with (otherwise you end up with "Could not load JasperReports report from class path resource [jasperreports/index.jasper]" type errors):

@Bean
public JasperReportsViewResolver getJasperReportsViewResolver() {
  JasperReportsViewResolver resolver = new JasperReportsViewResolver();
  resolver.setPrefix("classpath:/jasperreports/");
  resolver.setSuffix(".jasper");
  resolver.setReportDataKey("datasource");
  resolver.setViewNames("rpt_*");
  resolver.setViewClass(JasperReportsMultiFormatView.class);
  resolver.setOrder(0);
  return resolver;
}  

@Bean
public InternalResourceViewResolver getInternalResourceViewResolver() {
  InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  resolver.setPrefix("/WEB-INF/views/");
  resolver.setSuffix(".jsp");
  resolver.setOrder(1);
  return resolver;
}

And inside the controller:

@RequestMapping(value = "helloReport4", method = RequestMethod.GET)
public ModelAndView getRpt4(ModelMap modelMap, ModelAndView modelAndView) {
  modelMap.put("datasource", getWidgets());
  modelMap.put("format", "pdf");
  modelAndView = new ModelAndView("rpt_HelloWorld", modelMap);
  return modelAndView;
}

This is my preferred approach. Controllers resolve jasper reports in a very similar fashion to how jsp views are resolved using the InternalResourceViewResolver and there is therefore no need for an explicit mapping file as with the xml or properties file approach in method #3 above.

EDIT

The javadocs for JasperReportsPdfView mention it uses the deprecated JRExporter API. Is there a better (newer) JasperReports view to use? Perhaps opting for the JasperReportsMultiFormatView is a better option as it does not appear to use JRExporter.

Question:

I have declared a bean in jasper-view.xml as follows

<bean id="resultsPdf"
      class="org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView"
      p:url="classpath:reports/resultsPdfFormat.jrxml"
      p:reportDataKey="datasource">
    <property name="headers">
        <props>
            <prop key="Content-Disposition">
                attachment; filename=results.pdf
            </prop>
        </props>
    </property>
</bean>

The method in the controller is as follows

@RequestMapping(value = "/results-pdf", method = RequestMethod.GET)
@ResponseBody
public ModelAndView generateResultsPdf(@RequestParam("year") String year) {

    List<Student> studentList = studentService.getResultsByYear(year);
    JRDataSource jrDataSource = new JRBeanCollectionDataSource(studentList);
    Map<String, Object> parameterMap = new HashMap<>();
    parameterMap.put("title", "Results table"));
    parameterMap.put("datasource", jrDataSource);

    return new ModelAndView("resultsPdf", parameterMap);
}

At the moment it is working fine. The name of the downloaded file is

results.pdf

Now I would like to append the year parameter to the file name so that it would read as

results_YEAR.pdf

Any ideas? Thanks in advance.


Answer:

I figured out a way to resolve this matter. Instead of declaring a bean we can achieve the task by changing generateResultsPdf method

@RequestMapping(value = "/results-pdf", method = RequestMethod.GET)
@ResponseBody
public void generateResultsPdf(@RequestParam("year") String year, HttpServletResponse response) {

    List<Student> studentList = studentService.getResultsByYear(year);
    JRDataSource jrDataSource = new JRBeanCollectionDataSource(studentList);
    Map<String, Object> parameterMap = new HashMap<>();
    parameterMap.put("title", "Results table"));
    parameterMap.put("datasource", jrDataSource);

    try {
            JasperReport jasperReport = JasperCompileManager.compileReport(RESULTS_PDF_REPORT_PATH);
            JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameterMap, jrDataSource);

            String filename = "new-filename.pdf";

            response.setContentType("application/pdf");
            response.addHeader("Content-disposition", "attachment; filename=" +filename);
            OutputStream outputStream = response.getOutputStream();

            JasperExportManager.exportReportToPdfStream(jasperPrint, outputStream);
        } catch (JRException | IOException e) {
            logger.error("Error in generating pdf : {}", e);
        }
}

Question:

I wish use JasperReport with Spring MVC, but I use InternalResourceViewResolver as a handling method. And when I call my report method it starts searching pdfReport.jsp page and shows me a 404 not found. But I need open report file, not the .jsp page!

My Controller method :

@RequestMapping(method = RequestMethod.GET, value = "/report/{id}")
public ModelAndView generatePdfReport(ModelAndView modelAndView) {
    LOG.debug("--------------generate PDF report----------");

    Map<String, Object> parameterMap = new HashMap<String, Object>();
    java.util.List<Node> nodeList = nodeService.list();
    JRDataSource JRdataSource = new JRBeanCollectionDataSource(nodeList);
    parameterMap.put("datasource", JRdataSource);

    // pdfReport bean has ben declared in the spring configuration
    return new ModelAndView("pdfReport", parameterMap);
}

My spring xml file configuration:

<mvc:annotation-driven/>
<tx:annotation-driven/>
<context:component-scan base-package="com.datum.fnd">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="contentType" value="text/html; charset=UTF-8" />
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!-- Report pages beans  -->     
<bean id="pdfReport"
      class="org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView"
      p:url="classpath:reportTest.jrxml"
      p:reportDataKey="datasource" />

Answer:

I have solved my problem with the some changes :

1)Add follwing code to your spring xml configuration file :

<!--  Jasper report  -->
<import resource="jasper-views.xml"/>

<bean class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="location" value="/WEB-INF/spring/jasper-views.xml"/>
    <property name="order" value="0"/>
</bean>

2)Create new jasper-views.xml file , copy and paste follwing codes to it:

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:p="http://www.springframework.org/schema/p"
   xmlns:util="http://www.springframework.org/schema/util"
   xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util-3.0.xsd">

<!--here all the url value should contains the valid path for the jrxml file-->

<bean id="pdfReport"
      class="org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView"
      p:url="classpath:reportTest.jrxml"
      p:reportDataKey="datasource" />
</beans>