Tuesday, January 31, 2012

Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 2)

Review

In the previous section, we have laid down the functional specs of the application. In this section, we will discuss the project's structure and write the Java classes in layers.


Project Structure

Our application is a Maven project and therefore follows Maven structure. As we create the classes, we've organized them in logical layers: domain, repository, service, and controller.

Here's a preview of our project's structure:

The Layers

Domain

This layer contains two domain classes, User and Role. They represent our database tables, user and role respectively. Because we're developing a JPA-based repository, both classes must be annotated with JPA annotations.




Data Transfer Object (DTO)

This layer contains four DTO classes:
  • UserDto is a POJO for mapping User objects to and from the presentation layer
  • StatusResponse is a POJO for sending boolean responses to the presentation layer
  • JqgridResponse is a container for UserDto objects for sending records to the jqGrid table. It contains information regarding the number of rows, current page, and total pages.
  • JasperDto is a POJO for mapping User objects into something that Jasper can process easily (This is used for reporting purposes only.)

Note: We won't display the contents of UserDto, StatusReponse, and JqgridResponse here since we've already discussed that on a different tutorial (see Spring MVC 3.1, jqGrid, and Spring Data JPA Integration Guide (Part 2))



Controller

This layer contains two controllers, MediatorController and UserController.
  • MediatorController is responsible for redirecting requests from the root path to the Users page
  • UserController is responsible for handling user related requests, including download requests




Let's describe the methods available from UserController:
  • The records() method returns a list of UserDto objects as JSON strings. This will be displayed on the presentation layer as an interactive grid of data with the help of jqGrid plugin.
  • checkDownloadProgress() method is used to check if a download token is present or not. See Download Token Algorithm below
  • getDownloadToken() method is used to retrieve a download token. See Download Token Algorithm below
  • download() method performs the actual report download by delegating it to the DownloadService.

Download Token Algorithm
(Inspired by Detect when browser receives file download)

This is basically a fancy way of adding a progress box in an AJAX environment, so that we can display the status of a download requests. In a typical AJAX requests, the call is perform asynchronously, and the user does not know if the requests is being processed unless we provide a progress report. Unfortunately, you can't use AJAX directly for file downloads.

However, you can provide an illusion of a progress report through the Download Token Algorithm:
  1. User clicks on download button. A progress box appears.
  2. Download button performs an AJAX requests to retrieve a download token
  3. Once a download token has been received, a call to the actual download URL is performed. (This is not an AJAX requests)
  4. Periodically perform an AJAX requests to check if the download token retrieved earlier still exists. If not, the download has started.
  5. If the download token does not exists anymore, close the progress box.

Repository

This layer contains a single interface, UserRepository. This is our data access object (DAO). With the help of Spring Data JPA, Spring will automatically provide the actual implementation.

What is Spring Data JPA?

Spring JPA is part of the umbrella Spring Data project that makes it easy to easily implement JPA based repositories.

Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that's actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.

Source: http://www.springsource.org/spring-data/jpa



Service

This layer contains the following services:
  • TokenService: This is responsible for providing and managing download tokens. See the Download Token Algorithm above.
  • JasperDatasourceService: Wraps a list of objects into a JRBeanCollectionDataSource. This is the datasource for our Jasper reports.
  • ExporterService: This is responsible for exporting a Jasper report into different formats, such as Pdf and Excel.
  • DownloadService: This is responsible for the actual download requests and writing of reports to the output stream






The DownloadService is the heart of the download process. The algorithm is as follows:
  1. Add report parameters
  2. Retrieve template
  3. Convert template to JasperDesign
  4. Compile design to JasperReport
  5. Create the JasperPrint object
  6. Create an output byte stream where data will be written
  7. Export report
  8. Write to reponse stream

Utilities

  • JqgridFilter is a Java representation of a jqGrid filter
  • JqgridObjectMapper is used to convert a jqGrid filter to a JqgridFilter object
  • UserMapper is used to map User objects to UserDto objects
  • TraceInterceptor is an AOP-based utility class to help us debug our application. This is a subclass of CustomizableTraceInterceptor. See Spring Data JPA FAQ

Note: We won't display the contents of JqgridFilter, JqgridObjectMapper, UserMapper, and TraceInterceptor here since we've already discussed them on a different tutorial. See Spring Data JPA FAQ

Next

In the next section, we will create the Jasper report layout and design it using iReport. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3.1 and JasperReports: Using iReport and AJAX (Part 2) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

10 comments:

  1. Hi,
    when i run spring-jasper-tutorial i have an error due to

    2012-04-03 22:04:43.374:/spring-jasper-tutorial:INFO: Initializing Spring root WebApplicationContext
    [ERROR] [main 10:04:44] (ContextLoader.java:initWebApplicationContext:307) Context initialization failed
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.dao.annotation.PersistenceExceptionTranslati
    nPostProcessor#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with
    ame 'entityManagerFactory' defined in ServletContext resource [/WEB-INF/spring-data.xml]: Invocation of init method failed; nested exception is java.lan
    .NoClassDefFoundError: org/hibernate/cfg/AnnotationConfiguration

    Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in ServletContext resou
    ce [/WEB-INF/spring-data.xml]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/hibernate/cfg/AnnotationConfigu
    ation
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1455)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:400)

    Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in ServletContext resou
    ce [/WEB-INF/spring-data.xml]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/hibernate/cfg/AnnotationConfigu
    ation

    ReplyDelete
    Replies
    1. please help to resolve

      with name 'entityManagerFactory' defined in ServletContext resource [/WEB-INF/spring-data.xml]: I
      cation of init method failed; nested exception is org.hibernate.MappingException: invalid mapping
      ETA-INF/orm.xml

      Delete
    2. you seem to be missing hibernate jar in your class path

      Delete
    3. Please help me to resolve below error

      [17/12/12 23:44:46:962 IST] 0000002c SystemOut O [ERROR] [WebContainer : 3 11:44:46] (ContextLoader.java:initWebApplicationContext:307) Context initialization failed
      java.lang.NullPointerException
      at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:892)
      at org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor.predictBeanType(RepositoryInterfaceAwareBeanPostProcessor.java:78)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:584)
      at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1331)

      Delete
  2. File name in header have to be in surrounded with '"' to work in Chrome > 16 - see this issue

    ReplyDelete
  3. Thanks for the great post!
    One question: how to pass a messagesource to the jasperreport for i18n.

    I notice that in DownloadService you use the static method:
    JasperReport jr = JasperCompileManager.compileReport
    but in order to call IJasperExporterManager.setMessageSource you need an instance of JasperCompileManager

    ReplyDelete
  4. Hello, I got below erorr, Plz help me to resolve :)

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [org.krams.aop.TraceInterceptor] for bean with name 'customizableTraceInterceptor' defined in ServletContext resource [/WEB-INF/trace-context.xml]; nested exception is java.lang.ClassNotFoundException: org.krams.aop.TraceInterceptor
    Related cause: org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [org.krams.aop.TraceInterceptor] for bean with name 'customizableTraceInterceptor' defined in ServletContext resource [/WEB-INF/trace-context.xml]; nested exception is java.lang.ClassNotFoundException: org.krams.aop.TraceInterceptor
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:527)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:585)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:913)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:384)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:283)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:111)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4939)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5434)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

    ReplyDelete
  5. Sir Krams, I would like to thank you for your code and great explanation. Code long and prosper! :)

    Att.,
    Marcos
    Ceará, Brazil.

    ReplyDelete
  6. And of course, learn and share, freely you receive, freely you give.

    ReplyDelete
  7. I have read your blog its very attractive and impressive. I like it your blog.

    Spring online training Spring online training Spring Hibernate online training Spring Hibernate online training Java online training

    spring training in chennai spring hibernate training in chennai

    ReplyDelete