Showing posts with label Spring. Show all posts
Showing posts with label Spring. Show all posts

Saturday, March 12, 2011

jQuery FlexGrid & Spring MVC 3

Recently I have been working on Spring MVC 3. There is inbuilt support for returning JSON data using @ResponseBody annotation. Just make sure that jackson-all-x.x.x.jar is on your classpath. I used jackson-all-1.7.4.jar.This works great.

I wanted to display the results in a tabular format with some basic sorting and paging functionality with AJAX. So I thought I will give a shot at jQuery. jQuery is such an amazing JavaScript library it makes this a lot easier when it comes to working with JavaScript or manipulating DOM.

I found this amazing jQuery plugin, FlexGrid. It was pretty much what I wanted. Though it does not support i18N or themed L&F, it did the job for me.

So I started integrating the pieces. jQuery FlexGrid expects the JSON data to be in a specific format:

   total: (no of rec)
   page : (page no)
   rows : [{id: idVal, cell: [ (col1 value) , (col2 value) ,.. ]},
           {id: idVal, cell: [ (col1 value) , (col2 value) ,.. ]}
          ]

But the data returned from the method with @ResponseBody annotation is in the following format:

   
{(col1 value) , (col2 value) ,..}

So I thought I might as well go ahead and modify my DAO's to return the data in the required format. But well, why should I modify my DAO's? Its after all a UI layer requirement. This is what I came up with.

Write a wrapper class that will hold the data required for FlexGrid and send this as a JSON response. Here's the wrapper class:

import java.io.Serializable;
import java.util.List;

/**
 * Wrapper class for JSON data to send to the client.
 * 
 * Currently we are using jQuery FlexGrid for displaying JSON data in table.
* * jQuery FlexGrid plug-in requires data to be in the below specified format. * * * total: (no of rec) * page : (page no) * rows : [{id: idVal, cell: [ (col1 value) , (col2 value) ,.. ]}, * {id: idVal, cell: [ (col1 value) , (col2 value) ,.. ]} * ] * * To keep the data service independent of this requirement as far as possible, * the id, cell format specifically ignored. we wrap the result from the data * service and further format the result using JavaScript as required. * * * @author Enterprise Integrals * * @version 1.0 * @see jQuery FlexGrid * * @param T generic data type for list of objects to be sent in the JSON response. */ public class JsonDataWrapper<T> implements Serializable { private static final long serialVersionUID = 1L; //current page private int page; //total number of records for the given entity. private long total; //list of records to be displayed. private List<T> rows; public JsonDataWrapper(int page, long total, List<T> rows) { this.page = page; this.total = total; this.rows = rows; } // getter setter }
Here is how it is used in Spring MVC 3
/**
* FlexGrid submits the following parameters
* page: current page
* rp: rows per page
* sortname: sorting done on which column:
* sortorder: sort order asc/desc
* query: search criteria if you are using search option
* qtype: search on which column in grid. this is again customizable.
* 
* These parameters can be used as filters in the DAO to get the data.
*
* By default the FlexGrid uses POST. You can change to GET.
* @param T the entity list to displayed in grid
*/
@RequestMapping(value="/some Path", method= RequestMethod.POST)
public @ResponseBody JsonDataWrapper<T> getRows(WebRequest request) {
 //get the current page posted from the grid.
 int page = Integer.parseInt(request.getParameter("page"));
 //get the list of objects to be displayed from the db calling the service
 //the total number of pages are dynamically calculated on the basis of the total number of rows in the table. get that.
 JsonDataWrapper<T> jdw = new JsonDataWrapper<T>(page, total rows, rows);
 return jdw;
}
But there is a catch here. The FlexGrid plugin again refuses to render the data. The reason is the List<T> rows is again a list of objects returned from the DAO layer. It does not format the data as required by FlexGrid plugin. How do we do that? So I decided lets not put this logic in our MVC implementation. Here is how the data is set on to the FlexGrid using JavaScript.

 
$("#flex1").flexigrid({
   // standard flexgrid configuration as per your need,
   preProcess: formatData
});

Note the preProcess definition. It pre processes the data before the grid is populated with the JSON data return from the server. You can inline the function that actually formats the data but I kept it separate. Here it goes:

function formatData(data) {
 var rows = Array();
 $.each(data.rows,function(i,row){
                //id can be mapped to any attribute of the return object in the list
  rows.push({id:row.val1, cell:[row.val1,row.val2]});
});
 
 return {
  total:data.total,
  page:data.page,
  rows:rows
 };     
}

Now the grid populated correctly. There are other grid plugins for jQuery but FlexGrid & jQGrid are the best. FlexGrid has lot less functionality as compared to jQGrid but I like it because its light. For advanced features you can have a look at jQGrid.

Sunday, October 5, 2008

Auto Injection of a Logger into Spring beans

Dependency injection using Spring provides an easy way of injecting logging implementations to beans configured using Spring. Spring inherently use (JCL) Jakarta Commons Logging which may cause class loading issues.
Refer to this post http://www.qos.ch/logging/classloader.jsp .
After searching for a while I found out that we can replace JCL with SLF4j by just replacing the commons-logging.jar with the SLF4j provided jcl-over-slf4j.x.x.x.jar implementation (Please add the required dependencies). SLF4j provides a lot of good features over good old logging implementation.
Please refer to http://www.slf4j.org.
Not going into further details lets start with our implementation of
Logger. The first step is to create a Logger annotation. The Logger annotation is shown below.

/**
* Indicates Logger of appropriate type to
* be supplied at runtime to the annotated field.
*
* The injected logger is an appropriate implementation
* of org.slf4j.Logger.
*/
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(FIELD)
@Documented
public @interface Logger {
}
Now lets define a class that actually does the job of injecting the logger implementation.
/**
 * Auto injects the underlying implementation of logger into the bean with field
 * having annotation Logger.
 * 
 */
import java.lang.reflect.Field;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;

import static org.springframework.util.ReflectionUtils.FieldCallback;

public class LoggerInjector implements BeanPostProcessor {

 public Object postProcessAfterInitialization(Object bean, String beanName)
   throws BeansException {
  return bean;
 }

 public Object postProcessBeforeInitialization(final Object bean,
   String beanName) throws BeansException {
  ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
   public void doWith(Field field) throws IllegalArgumentException,
     IllegalAccessException {
    // make the field accessible if defined private
    ReflectionUtils.makeAccessible(field);
    if (field.getAnnotation(Logger.class) != null) {
     Log log = LogFactory.getLog(bean.getClass());
     field.set(bean, log);
    }
   }
  });
  return bean;
 }
}
Using it is even simpler. Just add the Logger annotation created above to the Log field in the required class. The class DemoBean below shows a field annotated this way. The import class Log is actually present in the jcl-over-slf4j.x.x.x.jar and does not depend on the JCL anymore.
import org.apache.commons.logging.Log;

public class DemoBean {

 @Logger
 private Log log;

 public void doSomething() {
  log.info("message");
  log.error("Lets see how the error message looks...");
 }
}
Just define the above bean in your spring application context defition. (I used XML configuration). Also you need to define a bean using the class LoggerInjector. Application contexts can auto-detect BeanPostProcessor beans in their bean definitions and apply them before any other beans get created.

Monday, September 15, 2008

Exposing JAX-WS web service using Spring

We start by creating a web project in Eclipse. I used Sysdeo Tomcat plugin.This will create a basic web application structure. Note that this does not create a web.xml file in the WEB-INF folder.
Add the following jar files to the WEB-INF\lib folder

activation.jar
commons-collections.jar
commons-logging.jar
FastInfoset.jar
http.jar
jaxb-api.jar
jaxb-impl.jar
jaxb-xjc.jar
jaxws-api.jar
jaxws-rt.jar
jaxws-spring-1.7.jar
jsr173_api.jar
jsr181-api.jar
jsr250-api.jar
resolver.jar
saaj-api.jar
saaj-impl.jar
sjsxp.jar
spring.jar
stax-ex.jar
streambuffer.jar
xbean-spring-2.8.jar

You are required to download the jaxws-spring-1.X.jar from https://maven2-repository.dev.java.net/source/browse/*checkout*/maven2- repository/trunk/www/repository/org/jvnet/jax-ws-commons/spring/jaxws-spring/1.8/jaxws-spring-1.8.jar?rev=3913 (This is the latest available version. I used 1.7 version.)
If you are using Java SE 6 some of the jars may not be required. Also if you need to use the latest version of JAX-WS with Java SE 6 you will need to use the endorsed mechanism for loading the JAX-WS libraries.I am using Java SE 5. Here is the simple web service class:

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;

/**
* Please run the wsgen if you are using Style.DOCUMENT
* Style.RPC used only for demo purpose.
* @author
*
*/
@WebService
@SOAPBinding(style = Style.RPC, use = Use.LITERAL)
public class HelloService {

   @WebMethod
   public String sayHello(@WebParam(name="name")String name) {
       return "Hello " + name;
   }

}
Create an applicationContext.xml file in the WEB-INF folder as shown.


   
   

   

Create a web.xml file in the WEB-INF folder and add the following entries to it.

    contextConfigLocation
    /WEB-INF/applicationContext.xml




   
       org.springframework.web.context.ContextLoaderListener
   




   jaxws-servlet
   
      com.sun.xml.ws.transport.http.servlet.WSSpringServlet
   



   jaxws-servlet
   /hello



This service is configured using the <wss:binding> tag in the applicationContext.xml file as described above. <wss:binding> definitions define what services are exposed to which part of the URL space. You can also use the nested syntax for the configuration as shown below:
 
    
        
    

Server side Handlers for the service can also be configured using the <ws:service> definition as shown below:




If the service uses more than one handler, handlers can be nested.

   
       
       
   

Deploy the web project on tomcat (tested on Tomcat 6.0). The service WSDL can be accessed using the following URL http://<your-ip>:<tomcat-port>/<project-name>/hello?wsdl
Also I would suggest to have a look at Apache CXF.