Struts2 , JSON , JQGrid with annotations

This article explains my hardships which trying to get the example work. It is still not fully working solution but a solution which takes you through steps any new guys goes through while trying to configure struts2 using annotation. With introduction of annotation concept in Java5 , many framework’s tried to exploit it very well to make themselves developer friendly. With respect to Web Framework’s Spring MVC is the leader as well as frontrunner in using this. Struts2 has tried to implement the same concepts but for any new developer it be little hard to get it to work as there are not many working articles over the web. I never suggest using any fremawork tags in building UI elements which these framework’s profess ( It would be difficult to reuse/migrate if at all we have migrate in future). I never support people/framework’s who push UI development in hands of developers rather then to designer’s.

Saying this here is example of a sample struts2 application where i have a html named index.jsp which call a Struts2 Action in back-end to get data and presentation is defined in HTML

First of all i’ll define by build structure and dependencies. As i’m planning to use Maven my build structure is default maven build structure here is my pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.linkwithweb.struts2</groupId>
	<artifactId>Struts2Example</artifactId>
	<packaging>war</packaging>
	<version>1.0</version>
	<name>Struts2Example Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-core</artifactId>
			<version>2.1.8</version>
		</dependency>

		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-convention-plugin</artifactId>
			<version>2.1.8</version>
		</dependency>

		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-json-plugin</artifactId>
			<version>2.1.8</version>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>Struts2Example</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.1</version>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>tomcat-maven-plugin</artifactId>
				<version>1.0</version>
				<configuration></configuration>
			</plugin>

			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>jetty-maven-plugin</artifactId>
				<version>7.1.4.v20100610</version>
				<configuration>
					<webApp>${basedir}/target/medihits-admin</webApp>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

The struts2-convention-plugin-version jar file is needed if your are using annotations.

Struts2 has made life little simple by avoiding all the Configuration files it needed before this version by providing configuration by annotation if we just see our web.xml , it’s pretty straightforward where we configure Struts2 Filter to route all requests to corresponding actions

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
	<display-name>Struts 2 Sample Web Application</display-name>

	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/struts/*</url-pattern>
	</filter-mapping>

</web-app>

If you observer web.xml i’m filtering all requests that are starting with /struts/* using struts filter. This is entry point of struts application. Now let me describe how we define an Action in Struts2

package com.linkwithweb.user.action;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.ResultPath;

import com.opensymphony.xwork2.ActionSupport;
/**
*Example : http://localhost:8080/struts/User
*/
@Namespace("/struts/User")
@ResultPath(value="/")
public class WelcomeUserAction extends ActionSupport{

	private String username;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

        // Example URL : http://localhost:8080/struts/User/Welcome
	@Action(value="Welcome", results={
			@Result(name="success",location="pages/welcome_user.jsp")
	})
	public String execute() {

		return SUCCESS;	}
}

Looking at above code by now you might have understood how URL are mapped in Struts2.

Now let me describe my effort to add JSON-Plugin. If you have previously observed my pom i have added JSON plugin depdendency

		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-json-plugin</artifactId>
			<version>2.1.8</version>
		</dependency>

Here is some background how struts2 works

The Convention plug-in is the one which does everything in the background. The Convention plug-in does the following things.

  • By default the Convention plug-in looks for the action classes inside the following packagesstrut, struts2, action or actions. Here our package name is com.vaannila.action. Any package that matches these names will be considered as the root package for the Convention plug-in.
  • The action class should either implement com.opensymphony.xwork2.Action interface or the name of the action class should end with Action. Here we extend our WelcomeUser class from com.opensymphony.xwork2.ActionSupport which inturn implements com.opensymphony.xwork2.Action.
  • The Convention plug-in uses the action class name to map the action URL. Here our action class name is WelcomeUser and the URL is welcome-user. The plug-in converts the camel case class name to dashes to get the request URL.
  • Now the Convention plug-in knows which Action class to call for a particular request. The next step is to find which result to forward based on the return value of the execute method. By default the Convention plug-in will look for result pages in WEB-INF/contentdirectory.
  • Now the Convention plug-in knows where to look for results, but it doesn’t know which file to display inside the content directory. The Convention plug-in finds this with the help of the result code returned by the Action class. If “success” is returned then the Convention plug-in will look for a file name welcome-user-success.jsp or welcome-user.jsp . It need not be a jsp file it can be even a velocity or freemaker files. If the result value is “input” it will look for a file name welcome-user-input.jsp or welcome-user-input.vm or welcome-user-input.ftl.
Now id we need to define a Action which return’s JSON String, we need to define that action implements json-convention which is defined this way
package com.linkwithweb.ajax.action;

import java.util.ArrayList;
import java.util.List;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;

import com.opensymphony.xwork2.ActionSupport;

/**
 * @author Ashwin Kumar
 * 
 */
@Namespace("/struts/Ajax")
@Result(name = "success", type = "json")
@ParentPackage("json-default")
public class AjaxAction extends ActionSupport {

	@Action(
			value = "grid",
				results = { @Result(name = "success", type = "json") })
	public String execute() {
		return "success";
	}
/**
 * To Directly write to response and skip all Struts process flows use the below 2 methofs
 */
/*	public HttpServletResponse getResponse() {
		return ServletActionContext.getResponse();
	}*/

	/**
	 * Rename to execute to override
	 * 
	 * @return
	 * @throws Exception
	 *//*
	public String executeToOverrride() throws Exception {
		PrintWriter printWriter = null;
		getResponse().setContentType("text / xml;charset = UTF-8");
		getResponse().setHeader("Cache - Control", "no - cache");

		StringBuffer sb = new StringBuffer("<movie>");
		// let's say you have A Movie object "movie" that needs to be represented as XML
		try {
			printWriter = getResponse().getWriter();
			sb.append("<director>dir</director>");
			sb.append("<language>lang</language>");
			sb.append("<year>year</year>");
			sb.append("</movie>");
			printWriter.print(sb.toString());
		} catch (IOException e) {
			e.printStackTrace();
			throw e;
		} finally {
			printWriter.close();
			printWriter = null;
		}
		return NONE;
	}*/

	/**
	 * @return
	 */
	public String getPage() {
		return "1";
	}

	public int getTotal() {
		return 2;
	}

	public String getRecords() {
		return "13";
	}

	public List<String> getRows() {
		List<String> arrayList = new ArrayList<String>();
		arrayList.add("{'id':'13','cell':['13','2007-10-06','Client 3','1000.00','0.00','1000.00',null]}");
		arrayList.add("{'id':'12','cell':['12','2007-10-06','Client 2','700.00','140.00','840.00',null]}");
		arrayList.add("{'id':'11','cell':['11','2007-10-06','Client 1','600.00','120.00','720.00',null]}");
		arrayList.add("{'id':'10','cell':['10','2007-10-06','Client 2','100.00','20.00','120.00',null]}");
		arrayList.add("{'id':'9','cell':['9','2007-10-06','Client 1','200.00','40.00','240.00',null]}");
		arrayList.add("{'id':'8','cell':['8','2007-10-06','Client 3','200.00','0.00','200.00',null]}");
		arrayList.add("{'id':'7','cell':['7','2007-10-05','Client 2','120.00','12.00','134.00',null]}");
		arrayList.add("{'id':'6','cell':['6','2007-10-05','Client 1','50.00','10.00','60.00','']}");
		arrayList.add("{'id':'5','cell':['5','2007-10-05','Client 3','100.00','0.00','100.00','no tax at all']}");
		arrayList.add("{'id':'4','cell':['4','2007-10-04','Client 3','150.00','0.00','150.00','no tax']}");
		
		
		return arrayList;
	}
}
If you observe the above code any url request of form /struts/Ajax/grid will come to this Action class and as we defined it follows json-default convention and so this class acts as bean and this java bean is converted to json string in response here is sample response for above class
{"page":"1","records":"13","rows":["{'id':'13','cell':['13','2007-10-06','Client 3','1000.00','0.00','1000.00',null]}","{'id':'12','cell':['12','2007-10-06','Client 2','700.00','140.00','840.00',null]}","{'id':'11','cell':['11','2007-10-06','Client 1','600.00','120.00','720.00',null]}","{'id':'10','cell':['10','2007-10-06','Client 2','100.00','20.00','120.00',null]}","{'id':'9','cell':['9','2007-10-06','Client 1','200.00','40.00','240.00',null]}","{'id':'8','cell':['8','2007-10-06','Client 3','200.00','0.00','200.00',null]}","{'id':'7','cell':['7','2007-10-05','Client 2','120.00','12.00','134.00',null]}","{'id':'6','cell':['6','2007-10-05','Client 1','50.00','10.00','60.00','']}","{'id':'5','cell':['5','2007-10-05','Client 3','100.00','0.00','100.00','no tax at all']}","{'id':'4','cell':['4','2007-10-04','Client 3','150.00','0.00','150.00','no tax']}"],"total":2}
I have integrated JQGrid multiselect functionality in index.jsp. here is sample code which configures jqgrid multiselect
jQuery("#list9").jqGrid({
   	url:'response.jsp?time='+new Date().getTime(),
	datatype: "json",
   	colNames:['Inv No','Date', 'Client', 'Amount','Tax','Total','Notes'],
   	colModel:[
   		{name:'id',index:'id', width:55},
   		{name:'invdate',index:'invdate', width:90},
   		{name:'name',index:'name', width:100},
   		{name:'amount',index:'amount', width:80, align:"right"},
   		{name:'tax',index:'tax', width:80, align:"right"},		
   		{name:'total',index:'total', width:80,align:"right"},		
   		{name:'note',index:'note', width:150, sortable:false}		
   	],
   	rowNum:10,
   	rowList:[10,20,30],
   	pager: '#pager9',
   	sortname: 'id',
	recordpos: 'left',
    viewrecords: true,
    sortorder: "desc",
	multiselect: true,
	caption: "Multi Select Example"
});
jQuery("#list9").jqGrid('navGrid','#pager9',{add:false,del:false,edit:false,position:'right'});
jQuery("#m1").click( function() {
	var s;
	s = jQuery("#list9").jqGrid('getGridParam','selarrrow');
	alert(s);
});
jQuery("#m1s").click( function() {
	jQuery("#list9").jqGrid('setSelection',"13");
});
Here is how index.jsp looks like after running ( you can use mvn -Djetty.port=9090 jetty:run or mvn tomcat:run)
Will make it more professional and fix all issues along with article in coming week. Got to work on something else so leave it here
Code has been checked in to following svn location. https://linkwithweb.googlecode.com/svn/trunk/Struts
So play with this as you want

8 thoughts on “Struts2 , JSON , JQGrid with annotations

  1. Hi,
    checkout

    into this forder :
    C:\maj\practices\struts2\
    Struts2Example

    Please can you show me what to do with this folder Struts2Example to have it as a project in eclipse ?

    Thanks

  2. It’s already an eclipse project. You can directly import into eclipse by usin gimport existign projects option in eclipse. Incase you want to regenerate eclipse configuration files
    use this command
    mvn eclipse:eclipse

  3. hi Aswin its very good tutorial .. Can I have This same pagination using display tag including checkboxes Or SAME THIS TUTORIAL WITH OUT MABEN AND ANNOTATIONS AS I AM NOT AWARE OF THEM hThank you very much for your effort

Leave a comment